イベントビルダ
[注意] この文書はイベントビルダの概要を説明していますが,実際にシステムを構築するのに必要な項目を網羅しきれていません.実際の使用を検討する際は,メールフォーム(http://www.awa.tohoku.ac.jp/~sanshiro/kinoko-feedback/sendmail.html)より作者に連絡をください.使用状況に合わせた具体的な対応ができると思います.
基本動作
KiNOKO のイベントビルダは,基本的にはどのようなデータでも処理できるように構成されています.ただし,その内容はデータ断片を組み換え,並べ替え,整理することのみで,データ検証や解析的な機能は含みません.これらは別コンポーネントで行うことが想定されています.
KiNOKO のイベントビルダは,一つのデータソース中のデータをビルドする前半部分 (Horizontal Builder) と,複数のデータソースからの部分ビルド済データをビルドする後半部分 (Vertical Builder) から構成されます.それぞれの段階での処理は以下のようになっています.
- 前半:単一データソース中でのソーティング (Horizontal Builder)
- 一つのデータソースから渡されるデータ断片(EventFragment)の列を「イベント番号(EventKey)」と「チャンネル(ChannelKey)」で特徴付けることのできるイベント断片(EventPiece)に再構成(分解と再結合)する.
- それぞれのイベント断片をイベント番号順に並べ替える.同じイベント番号の断片はチャンネル番号順に並べる.同じイベント番号で同じチャンネル番号の断片は到着順に並べる.
- 同じイベント番号を持つイベント断片を集めてひとかたまりのイベントセグメント(EventSegment)を構成し,出力する.
- 後半:複数データソースの結合 (Vertical Builder)
- 複数のデータストリームから渡されるイベントセグメントをイベント番号順に並べる.同じイベント番号のセグメントはセグメント番号順(ユーザ指定)に並べる.同じイベント番号の同じセグメント番号のセグメントは到着順に並べる.
- 同じイベント番号を持つイベントセグメントを集めてひとかたまりの新しいイベントセグメントを構成し,出力する.
これらは通常はコンポーネントとして実装され,典型的にはバッファを介して以下のように接続されます.
KiNOKO のイベントビルダは,入力されるデータのフォーマットについて何も知りません.入力データからイベント断片を切り出し,イベント番号とチャンネルの情報を取得するために,Horizontan Builder の入力部分にユーザ定義の「スキャナ」クラスが登録されています.ビルディングに必要なその他のパラメータはビルダ構成ファイル (Builder Configuration File) に記述され,実行時に解読されます.
イベントビルダを使うために,ユーザは以下のことを行わなければなりません.
- 扱うデータフォーマットに合わせてスキャナクラスを作成する
- スキャナクラスを Horizontal Builder に登録しコンパイルする
- ビルダ構成ファイルを作成し,パラメータを設定する
- KCOM スクリプトを編集してビルダコンポーネントを組み込む
イベントビルダの構造
概要
汎用フレームワーク
KiNOKO のイベントビルダは,基本的にはどのようなデータでも処理できるように構成されています.そのため,その内容はデータ断片を組み換え,並べ替え,整理のみで,データ検証や解析的な機能は含みません.これらは別コンポーネントで行うことが想定されています.
ここでいうイベントのビルディングとは,複数の入力から送られてくるデータ列を,意味のあるデータ断片に分割し,同一イベントに属するデータ断片を集約することを指します.すべてのデータ断片にイベント番号がついているなら,この集約部分はデータ断片をイベント番号順にソーティング(並べ替え)することに対応します.加えて KiNOKO は,同一イベントに属するデータ断片を,チャンネル番号順にソーティングします.
結局のところ,ビルディングとは,データの切り分けとソーティングをすることだけなので,データの切り分け方法と各断片のイベント番号およびチャンネル番号の取り出し方法さえ分かれば,ビルディングのほぼ全ての部分を汎用のフレームワークで構成できることになります.KiNOKO では,これらの情報の取得にユーザ定義のスキャナクラスを使うことによって,アプリケーション依存部分を切り分け,主要部分の汎用化を実現しています.ビルダの構成に必要なパラメータ(ビルド対象チャンネルやバッファサイズなど)は実行時に設定ファイルから読み込むようになっています.
破損データストリーム
ビルダに入力されるデータが完全ならば,上記のフレームワークで全ての処理を行うことができますが,データ収集系の不具合などによりデータが不完全だった場合,切り分けやイベント番号の取り出しができないことがあります.また,コンピュータやネットワーク等の状況によっては,一部のデータが遅れて到着し,ビルディングに間に合わないこともあります.KiNOKO では,このような状況でもビルダの安定動作を確保し,かつ,ビルダに入力されたいかなるデータも捨てないという立場から,このようなデータにも適切なタグをつけて正常部分と区別できる状態で出力するようになっています.
ベストエフォート動作
イベントビルダの動作を不安定にさせる原因の一つに,一部のチャンネルからのデータが到着せず,ビルダがそれを長時間待つことによってバッファメモリを使い尽くしてしまうことがあります.これは,ネットワーク負荷などにより単純にデータ到着が遅れるような場合から,何らかの障害によってデータが失われたり,データの一部が破損し正しく認識できなくなる場合などに生じます.KiNOKO では,このような状況においてもビルダ自身の安定性を保ち,正常チャンネルのデータを守るため,到着の遅れたデータを永久に待つような動作をしません.具体的には,バッファメモリの使用量が指定量を越えた場合,それ以上のデータを待たずにバッファメモリ内のデータを強制的にビルディングします.
この動作により,到着の遅れたデータが本来入るべきイベントの中に入らないということが生じえます.KiNOKO では,このようなデータは上記の「破損データストリーム」に Late Arrival として送り出します.Late Arrival データは明確に区別され分けられているので,オフライン解析で再構成することが容易です.また,状況によっては,ビルダの後段のバッファで追いついて,正しい場所に挿入されることもあります(Late Arrival パケットはビルダのバッファを素通りするので,実際に追いつく可能性はかなりあります).
このような多段ビルダの特性を積極的に利用し,Late Arrival を処理するためだけに何もしないビルディングステージを追加することもできます.また,追加のビルディングステージをオフラインで実行すれば,Late Arrival の再構成を性能の高い計算機上で時間をかけて行うことができます.
ダイナミックテーブル
ビルディング対象のチャンネルなどは設定ファイルでビルダに通知されますが,KiNOKO のビルダは他の一般的なビルダと異なり,厳格な入力チャンネルテーブルを構成しません.上述したように,KiNOKO のビルダは入力データを切り分けてソートすることだけに徹するので,厳格なテーブルを持つ必要がないためです.
KiNOKO のチャンネルテーブルは,データ自身によって構成されます.すなわち,新たなチャンネルのデータが到着すれば,その時点でテーブルにエントリが作成されます.チャンネルのアクティビティは常にモニタされており,アクティビティが止まったと判断されると,そのチャンネルはテーブルから削除されます.この判断のためのパラメータはビルダ構成ファイルに記述されます.
チャンネルテーブルは,イベントの構成要素が完全に揃ったかどうかの判断に使用されます.すなわち,チャンネルテーブルの中のすべてのチャンネルが,(実際にそのチャンネルにデータがあるかに関わらず)あるイベント分のデータが揃ったと報告すれば,そのイベントのビルディングが開始されます.テーブルを動的に構成することより,汎用性と安定性が向上します.例えば,ある特定のチャンネルを状況により使わないことにしても,ビルダの変更は必要ありません.特定のチャンネルが突然死亡しデータを送出しなくなったとしても,それによってビルダがデータを待ち続けるという状況が起こりません.
この方法による問題は,最初のイベントをビルドする段階でテーブルが完成していないと,ビルディングが不完全になってしまうことです.すなわち,最初のビルドを試みるときまでに,全ての対象チャンネルから少なくとも一つのデータ断片が届いている必要があります.KiNOKO では,イベントのビルディングを開始するためのいくつかの判断基準を用意しており,これらはビルダ構成ファイルに記述されます.ただし,次に述べる理由により,これが完全である必要はありません.
KiNOKO は,ビルディング開始条件が成立していなくても,バッファメモリが一杯になる直前に強制的にビルディングを開始します.もしこの段階でもデータ断片が一つも届いていないチャンネルがあり,そのチャンネルからのデータ断片が最初のイベントに含まれるなら,ビルドは不完全となります(この場合,当該データ断片が届いた段階で Late Arrival として処理されます).なお,このような状況下では,仮にテーブルがダイナミック構成でなくて最初から完全であったとしても,バッファフルによる強制ビルドの段階で当該データがバッファメモリに届いていないので,いずれにしろ Late Arrival となり,結果は変わりません.
用語
- EventKey
- 一つのイベントに対して一つの値となる識別値で,ビルドの対象となる全てのデータ断片がこの値を保持していることが想定されています.イベントビルダは,同じ EventKey を持つデータ断片を集めて一つのかたまりを構成します.概念的には,データ断片に対する時間方向の識別値です.実際には,一般に,イベントIDやイベントカウンタ,タイムスタンプ,フレーム番号,シーケンス番号などと呼ばれているものが対応します.現在のところ,符号付き 64bit 整数が割り当てられています.この値は連続である必要はありません.
- StreamKey
- ビルドの対象となるデータ断片の発生元を識別する値で,ビルドの対象となる全てのデータ断片がこの値を保持していることが想定されています.イベントビルダは,StreamKey の値ごとにイベント断片の到着が完了したかの判断をし,また,StreamKey の値ごとに,データレート等をモニタして生死の判断をします.概念的には,データ断片に対する空間方向の識別値で,時間変化しないものです.実際には,一般に,チャンネルやケーブル番号,モジュール番号,アドレスなどと呼ばれているものが対応します.Kinoko のデータソースIDやセクションIDなども StreamKey の一種として扱うことができます.現在のところ,符号付き 32bit 整数が割り当てられています.これも連続である必要はありません.
- ChannelKey
- StreamKey のひとつで,イベントビルダにとっての最小の空間方向の分割単位です.通常は,データ収集デバイスへの一つの入力信号ケーブルに対応します.イベントビルダはデータブロックが ChannelKey と EventKey で特定されるレベルまで分割されると,それ以上の中身については関知しません.ChannelKey でない StreamKey の例としては,ブロック読み出しを行うモジュールから読み出したデータのモジュール番号(Section ID など)や,クレート単位など部分的にビルドしたデータブロックに対する空間的識別値があります.
- DataPacket
- コンポーネント間を KinokoStream で転送されるデータのかたまりです.中身はビルディングのステージごとに異なったものとなります.
- DataChunk
- データのかたまりの一般的表現で,中身には関知しません.概念的には,単なる先頭アドレスとサイズで指定される連続データ領域です.実装上は,複数の不連続領域をまとめて一つのかたまりとして扱えるようになっています.
- EventFragment
- イベントを構成する(かもしれない)データのかたまりで,イベント単位,チャンネル単位に切り分けられていない段階のものです.つまり,複数のチャンネルからの複数のイベントの断片が混ざっている(かもしれない)状態です.この段階では,ある種の StreamKey はあるものの,ChannelKey や EventKey はありません.この段階における StreamKey の例としては,一枚のモジュールからブロック読み出ししたデータに対するモジュール番号などがあります.
- EventPiece
- イベントを構成する(かもしれない)データのかたまりで,イベント単位,チャンネル単位に切り分けられたものです.それぞれの EventPiece について,EventKey および ChannelKey が割り当てられます.また,中身の状態(破損していないかなど)や前後関係(到着遅れなど)を示す EventPieceType 属性を持っています.
- EventPieceType
- EventPiece の中身を分類する整数値で,以下の値をとります.この値によって,ビルダの処理内容が制御されます.
- Normal
- 正しい EventKey および正しい ChannelKey を持ち,同じ StreamKey を持つ前後の EventPiece との間で正しい順序関係(EventKey が広義に昇順)を持っているものです.通常のビルディング処理が行われます.
- Padding
- 保存の必要がないデータ領域で,ChannelKey や EventKey が割り当てられていないものです.アライメントのためのパディングやデータチェックのためのパターンなどが対応します.ChannelKey や EventKey が必要になった段階でデータストリームから削除されます.意味的に Padding でも,削除したくない場合は適切な ChannelKey および EventKey を割り当てた上で Normal Piece とします.
- Unalinged
- 正しい EventKey および正しい ChannelKey を持つものの,同じ StreamKey を持つ前後の EventPiece との間の順序関係が正しくない(可能性がある)ものです.データ遅れによる見切りビルディングで生成された LateArrival や,何らかのバイパスに伴う EarlyArrival などがあります.ビルダは,多くの場合,この種類の EventPiece のために特別の処理をします.
- Corrupted
- ハードウェア障害などによりデータが壊れ, EventKey や ChannelKey が特定できないものです.データが可変長の場合は,データ境界が特定できず複数のイベントやチャンネルからのデータが混じる場合があります.破損データの出現位置を保持するために StreamKey や ChannelKey に値が割り振られることがありますが,これらの値はビルダ自身によっては使われません.多くの場合,ビルダのほとんどの処理を素通りします.
- EventSegment
- 同じ EventKey を持つ EventPiece を0個以上まとめたものです.ビルドの途中で部分的にまとめたものである場合と,最終結果として全てをまとめたものである場合があります.
- EventChopper
- イベントビルダに入力されるデータ列 (EventFragment) を EventPiece に切り分ける機能を担うビルダの主要構成要素です.Chopper は EventFragement を入力 StreamKey に応じて整理し,必要なら複数の EventFragment の分解再結合を行います.
- EventSorter
- 切り分けられた EventPiece を EventKey および ChannelKey 順に並べる機能を担うビルダの主要構成要素です.
- EventSegmentPacker
- 複数のソート済み EventPiece をまとめて EventSegment を生成する機能を担うビルダの主要構成要素です.逆の機能を持った Unpacker もあります.
- EventPieceScanner
- EventChopper によって使われるユーザ定義のクラスで,Chopper により DataChunk として渡される EventFragment を解読し,サイズ・EventKey・ChannelKey を取り出します.Chopper はこの情報を使って DataChunk の再結合や EventPiece の生成を行います.
- HorizontalBuilder
- ビルダのうち,一つのデータソースからのデータをビルドするもので,Chopper,Sorter および Packer から構成されます.もしビルド対象に一つのデータソースしかない場合は,出力される EventSegment はイベント全体が完全にビルドされたものになります.そうでない場合は,すべての HorizontalBuilder からの出力を Buffer コンポーネントや kdfmerge コマンドなどで結合し,次の VerticalBuilder で処理する必要があります.
- VerticalBuilder
- ビルダのうち,複数のデータソースからのデータをビルドするもので,Sorter および Packer から構成されます.各データソース内のデータは HorizontalBuilder によってすでにビルドされていなければなりません.出力される EventSegment はイベント全体が完全にビルドされたものになります.
動作
- デフラグと EventKey・ChannelKey の取得 (Horizontal Builder のみ)
- Horizontal Builder に渡されたデータ列 (EventFragment) は,StreamKey ごとに分けられ,さらにそれぞれが EventPiece に切り分けられます.EventFragment から EventPiece の切り出しは,ユーザスキャナの助けを借りて,先頭にある EventPiece のサイズ分を取り出すことにより行われます.EventFragment が EventPiece のサイズよりも小さい(途中で分断されている)か,小さすぎてサイズを計算できない(サイズが記録されているフィールドが含まれていないなど)場合は,同じ StreamKey を持った次のデータの到着を待ち,それを後ろに結合して同じ処理を繰り返します.
この段階でユーザスキャナがデータ破壊を検出したら,その部分は以降破損データ (Corrupted) として処理されます.
EventPiece の切り分けができたら,やはりユーザスキャナの助けを借りて,EventKey および ChannelKey を取得し,その情報を EventPiece に付加します.
- イベント完成判定
- EventPiece へ切り分けられたデータは,ChannelKey ごとに EventKey を管理するテーブルに登録されます.すべてのチャネルにある EventKey のイベントが揃ったと判断されると,その EventKey をもつ EventPiece がまとめられて EventSegment が構築され,出力されます.
あるチャンネルにあるイベントが揃っているかどうかは,そのチャンネルにそれ以降のイベントのデータが到着しているかによって判定されます.特定のチャンネルにおいて,一つのイベントには高々一つの EventPiece しか含まれないということが確定している場合,ユーザスキャナがそのことを示すフラグを立てることにより,イベント完成判定基準が緩和されます.Unaligned とマークされている EventPiece は,この判定対象から除外されます.
完成イベントが存在しない状態でバッファが一杯になった場合,古いイベントから順に強制的にビルドされていきます.もし本来含まれるべき EventPiece がこの後到着したら,それは Unaligned とマークされそのまま出力されます (Late Arrival).強制ビルディングの際に完成判定基準を満たしていなかったチャンネルはイベントビルダが想定するデータレートでデータを送っていないとみなし,「死亡」とマークされ,以降のイベント完成判定対象から除外されます.もしこの後でそのチャンネルのデータが届いた場合,「死亡」状態は解除され,もとどおりの動作となります.
- チャンネルテーブルの構成
- イベント完成判定で「すべて」のチャンネルの到着データを比べる際,「すべて」を定義するのがチャンネルテーブルです.基本的には,新しい ChannelKey のデータが到着するたびにチャンネルテーブルにエントリが追加されます.チャンネルテーブル中のチャンネルのデータレートは常にモニタされ,データが到着しなくなったと判断される(次節)と,「すべて」の対象から一時的に除外されます.
このようにデータの到着によってテーブルを動的に構成する場合,最初のイベントの完成判定を行う時点でイベントテーブルが完成している必要があります.そうでないと不完全なテーブルを元に不完全なビルディングを行うことになってしまうためです(それでも,Late Arrival として後段で適切に処理することが可能です).KiNONO はイベントビルディング開始の条件を複数用意し,構成ファイルから指定できるようになっています.
もっとも単純な開始条件は,到着データの総量を指定するものです.読み出しスクリプトの制御により,例えば最初の 10MB には各チャンネルのデータが必ず最低1つ含まれていることが保証できれば,この値を指定することができます.
若干高度な方法として,各チャンネルのデータを含んだパケット数を計算して,「テーブルにすでに登録されているすべてのチャンネルに対してそのチャンネルが指定数以上のパケットデータを生成した」ことを開始条件とすることができます.多くの場合パケット数は読み出しのサイクル数に対応するので,すべてのチャンネルデータの到着を保証しやすくします.
一番安全な開始条件は,開始条件を指定しないことです.この場合,最初のイベントはバッファが一杯になることによる強制ビルディングとなります.この時点でもデータが到着していないデータがある場合でも,その部分はどのような方法をとったとしても Late Arrival となり,結果は変わりません.この方法の欠点は,ビルディング開始時に警告が出ることとと,最初のイベントのビルディングまでの遅延が大きいことです(最初の欠点は開始条件にバッファサイズより若干小さい到着総データサイズ条件を指定することにより回避できます).
全く別の方法として,チャネルテーブルを構成ファイルから静的に記述することもできます.テーブル中に記載されたチャンネルからデータが到着しないとビルディングが開始されないので,その中に未使用チャンネルができた場合はバッファが一杯になるまで待ち続けることになります.ただし,この場合でも,最初の強制ビルディングにより未使用チャンネルが「死亡」とマークされるので,その後は問題なく動作することになります.
- 死亡チャンネルと生き返りチャンネル
- すでに何度か言及しているとおり,KiNOKO はチャンネルごとにアクティビティをモニタし,チャンネルテーブルを動的に構成します.明らかにアクティビティが下がったと判断されるチャンネルまたはイベントビルダの動作に支障をきたすほどにアクティビティの低いチャンネルは「死亡」とマークされ,イベント完成判定から除外されます.
チャンネルが「死亡」したと判断されるのは,以下のいずれかの条件を満たした場合です.
「死亡」チャンネルが「生き返る」のは,以下のいずれかの条件を満たした場合です.
データフォーマット
イベントビルダが出力するデータのフォーマットを以下に記します.ただし,通常の使用ではフォーマットの詳細を意識する必要はありません.ビルダが出力したファイルの読み方は 出力ファイルの読み方 の項を参照してください.
- データソース名: ビルダ構成ファイルで指定したもの
- セクション名: EventSegment
datasource "MyEvent01"<13310>
{
section "event_segment"<16385>: block {
attribute ContentFormat = "EventSegment";
attribute ContentFormatVersion = "1.0";
}
}
EventSegment セクションは block 型で,その中身は以下のようになっています.
ここで,<EventPiece>* は可変数個の可変長ブロックで,ひとつの可変長ブロックは以下のような構造になっています.
Word は全て 32bit 幅です.現在のところ,データブロックサイズは 4 byte の倍数のみ (32bit 幅ブロックのみ) サポートされています.
必要バッファサイズの見積り
イベントビルダの内部バッファのサイズは,Late Arraival の発生頻度が十分小さくなるように設定する必要があります.Horizontal Builder と Vertical Builder の間のバッファコンポーネントのバッファは,ストリームを結合することだけが目的なので,データがスムーズに流れる限りサイズは重要ではありません.Vertical Builder は入力データが読み込み可能になり次第すぐにそれを内部バッファへ取り込みます.
必要内部バッファサイズの計算方法
ここでは全てのチャンネルが一様に確率的にデータを生成するとして,偶発的に発生する Late Arraival の頻度を設定限界以下にするバッファサイズの計算をしてみます.なお,バッファサイズが EventPiece のサイズに大して十分に大きい(Late Arraival の許容出現頻度がデータ頻度よりも数桁以上小さい)ような状態で使用することを前提にして,近似計算を行います.
- 一つのチャンネルの一つの EventPiece のサイズを s とする.
- ビルド対象のチャンネル数を N とする
- s と N の積として特定チャンネルの平均イベント出現間隔 L=s*N が求まる
- 作業バッファのサイズを S とすると,バッファフル状態での特定チャンネルのバッファ中の平均イベント数 n が S/L として求まる
- あるバッファフルの状態で,バッファ中のイベント数が 1 以下になると Late Arrival となる.ある特定チャネルについて,この確率 p はポアソン分布で求められ,近似的に n/sqrt(n) シグマとなる
- あるバッファフルの状態で,少なくとも一つのチャンネルが Late Arrival となる確率 P は,P = 1 - (1-p)**N で,近似的に P = n*p.
- 連続動作状態を,毎回バッファフルまで貯めてから1イベントをビルドするモデルで考えると,イベントごとに上記の試行を行うことになる.
- 毎秒イベント数が f で,P が十分に小さければ,時間 T 秒の間に起こる Late Arrival の平均数は近似的に f*T*P 個.
- f*T*P 値が指定値以下になるように S の大きさを決める
バッファサイズが十分に大きい状態では,Late Arrival の出現確率はチャンネルの占拠率や読み出しブロックの大きさには依存しないことに注意してください.
計算例
- EventPiece のサイズ : 256 byte
- チャンネル数: 4000
- イベントレート:1000 Hz
- 許容できる偶発 Late Arrival 数: 1 年間で 0.1 個
- 1年間のイベント数: f*T = 1000 * 365*24*60*60 = 3.2e+10
- 1イベントあたりの Late Arrival 確率 P の上限: P < 0.1 / 3.2e+10 = 3.2e-12
- 1イベント1チャンネルあたりの Late Arrival 確率 p の上限: p = P / 4000 < 7.9e-16
- p の上限値はたぶん 10 シグマくらいなので,n の下限値は n > 10**2 = 100 くらい
- チャンネルあたり平均出現間隔 L は L = 256 * 4000 = 1 Mbyte
- 必要バッファサイズ S は S = n * L > 100 M byte
Horizontal Builder でも Vertical Builder でも計算は基本的に同じですが,Horizontal Builder の方がビルド対象チャンネル数が小さくなるはずです.Vertical Builder は Horizoltal Builder が部分ビルドしたものを受け取るのでチャンネル数は Horizontal Builder の数と思うかもしれませんが,実際には,イベント出現間隔が重要なので,大元のチャンネル数と大元の EventPiece のサイズを使って計算しても結果に大きな違いはありません.
Horizontal Builder において,モジュールからの読み出しサイズがバッファサイズに比べて十分小さくないと,上記計算のチャンネルあたりイベント出現間隔のポアソン的扱いが妥当でなくなります.計算の妥当性を保証するという意味で,バッファサイズは読み出しサイズよりも十分大きいほうが安全です.Vertical Builder では読み出しブロックが解体されているのでこの問題はありません.
チャンネルからのデータ出現が確率的かつ一様でない場合は上記の計算はそのままでは適用できません.チャンネルごとに占拠率が大きく異なる場合は,イベント出現間隔を低頻度チャンネルに合わせて計算しなおします(必要バッファサイズは占拠率比に反比例します).特定のイベント(区間)が特異など,時間的に大きな変動がある場合は,そのイベント(区間)を別に計算して要求サイズの大きい方をとるか(特異性によっては和をとる),そのイベント(区間)をまるごと追加収容できるようにバッファサイズを設定するかします.Vertical Builder では,Horizoltal Builder による前処理のため,この問題の影響は比較的小さい(ほとんどの場合は全くない)ことが多いはずです.
ビルダの動作にとって,内部作業メモリは大きければ大きいほどよいので,状況が許す限り大きめにとるのが安全です.上記計算の数倍程度の余裕を持っておくと安心です.
使用手順
概略
- モジュールのデータフォーマットに合わせたユーザスキャナクラスを作成する.
- TKinokoHorizontalBuilderCom にユーザスキャナを登録してコンポーネントを作成する.
- ビルダ構成ファイルを作成する.
- ここまでで作成したものを単体テストする.
- KCOM スクリプトを編集し,ビルダコンポーネントを配置接続する.
- 実行中に Late Arrival が出た場合,オフラインで Unaligned Piece を再構成する.
ここで使用しているサンプルの完全なコードが kinoko/local/tutorial/EventBuilder に置いてあります.
ユーザスキャナクラスの作成
ユーザスキャナクラスは,ビルダに入力される EventFragment を読んでその先頭にある EventPiece のサイズおよび EventKey や StreamKey などを Kinoko に教えるものです.実際には,Kinoko はユーザスキャナに先頭アドレスが設定された EventPiece オブジェクトを渡すので,ユーザスキャナはそれに EventKey や StreamKey の値を設定し,先頭の EventPiece のサイズを Kinoko に返します.
Kinoko の中で抽象クラス TKinokoEventPieceScanner が宣言されているので,これを継承し,コンストラクタ,デストラクタと以下の2つのメソッドを実装します.
class TKinokoEventPieceScanner: public TKinokoEventPieceDefs {
public:
TKinokoEventPieceScanner(void);
virtual ~TKinokoEventPieceScanner();
virtual int MaximumPieceSize(void) = 0;
virtual int Scan(TStreamKey StreamKey, TKinokoEventPiece& EventPiece) = 0;
};
- int MaximumPieceSize(void)
- 処理対象のデータに対して可能性としてありうる最大の EventPiece サイズをバイト単位で返す.この値は Kinoko のエラーチェックに使われる.
- int Scan(TStreamKey StreamKey, TKinokoEventPiece& EventPiece)
- 引数に EventPiece として渡されるデータを読み,そこから先頭の EventPiece を切り出して,その情報を EventPiece のメンバ変数に設定するとともに,その EventPiece のサイズを返す.渡されたデータに EventPiece が完全に含まれていない場合もしくはその判定ができない場合は 0 を返す.
- TKinokoEventPiece::TPieceType PieceType
- この EventPiece のタイプ.PieceType_Normal, PieceType_Padding, PieceType_Unaligned, PieceType_Corrupted のいずれか.
- TStreamKey ChennelKey
- この EventPiece の ChannelKey
- TEventKey EventKey
- この EventPiece の EventKey
- int PieceFlag
- この EventPiece の特性に関する付加的な情報.以下のビットフィールドの OR を設定する.特になければ 0 を設定する.
- PieceFlag_IsPossiblyEarlyArrival
- この EventPiece が何らかの理由により同一の _ChannelKey の他の EventPiece よりも早く到着した可能性があるときに設定する.
- PieceFlag_LastOfTheEvent
- この _ChannelKey のこの _EventKey の EventPiece がこの後にもう来ないことが確実な場合に設定する.
Scan() メソッドに渡される TKinokoEventPiece 型は次のように宣言されています.TKinokoDataChunk クラスから派生していることに注意してください.
class TKinokoEventPiece: public TKinokoDataChunk {
public:
int PieceType;
TStreamKey ChannelKey;
TEventKey EventKey;
int PieceFlag;
};
Scan() メソッドでは,DataChunk のインターフェースを使ってデータを読みます.TKinokoDataChunk は,32bit 整数の配列としてその中身にアクセスできます.また,Size() メソッドによって全体のサイズを取得できます.
class TKinokoDataChunk {
public:
inline const U32bit& operator[](unsigned WordIndex) const;
inline int Size(void) const;
引数に渡される StreamKey は,ビルダ設定ファイルで記述したものになります.
Scan() 関数が正の値を返すと,Kinoko は DataChunk から PieceSize 分を取り出して処理し,残りについて再び Scan() 関数を呼び出します.Scan() 関数が 0 を返すと,Kinoko はパケットが完結していないとし,同じ StreamKey の次のパケットを DataChunk の後ろにつなげて再び Scan() 関数を呼び出します.この過程で DataChunk のサイズが MaximumPieceSize() 関数が返す最大サイズに達した場合,Kinoko は全体を「破損データ」として処理し,次のパケットから再び Scan() による処理を開始します.
IsPossiblyEarlyArrival が設定されると,この EventPiece はビルディング待ちの列からはずされ,適切な挿入位置が見つかるまで退避されます.適切な挿入位置とは,同じ ChannelKey の EventPiece に対して,より大きい値の EventKey の直前です.退避された EventPiece は,EventKey の昇順チェックからはずされます.すでに LateArraival となっている場合は,この設定にかかわらず,LateArrival として処理されます.
通常,イベントのビルディングは全てのチャンネルの EventPiece が揃った段階で行われ,その判断は同じチャンネルに次のイベントの EventPiece が到着することによって行われます.IsLastOfTheEvent フラグが設定されていると,次の EventPiece の到着を待たずにこの判断が行われます.これによりビルディング開始までの待機時間が短縮されるので,バッファの使用量を減らす効果があります.一つのイベントの一つのチャンネルが必ず一つ(またはゼロ個)の EvnetPiece からのみ構成されると確実な場合にこのフラグを設定してください.
EventPiece がパディングでビルディング対象のデータでない場合は,PieceType に Padding を指定してください.この場合 EventKey や ChannelKey は使われないので設定しないままで構いません.ただし,戻り値に正しいサイズを返す必要があります.
データが破損していて EventKey および ChannelKey 取得できない場合は,PieceType に Corrupted を指定してください.戻り値には Corrupted としてビルディングから除外する範囲のサイズを返します.破損範囲が分からない場合もしくは MaxPieceSize を越えて大きい場合は渡された EventPiece の終了までを指定すれば,残りの破損部分は次の EventPiece で引き続き破損として処理することができます.破損データであっても EventKey および ChannelKey を任意の値で設定することができます.この値はビルディング処理では無視されますが,データとともに保存されるので,後から破損部分を特定する際の目印として使うことができます.もし EventKey や ChannelKey に負の値を指定すれば,直前の同じ StreamKey の正常な EventPiece のものと同じ値が Kinoko により書き込まれます(この場合に最初の EventPiece が破損していた場合は 0 が書き込まれます).
EventKey の値が正しくないのに Normal として処理されると,他の正常なものが異常と判断されることがあります.特に,不正ビットなどにより誤って遠い未来の値を示している場合,その間の全ての正常データが全て Out-Of-Order Arrival となってしまいます.この問題を避けるため,Scan() メソッドにおいて可能な限り EventKey の正当性チェックを行い,エラーが検出された場合は PieceType に Unaligned または Corrupted と指定してください.Corrupted ではなく Unaligned とした場合は,kinoko はなるべくそのデータを正しい場所に入れようと努力し長期間保持するので,それが不要な場合は Corrupted とするほうが大幅に負担を減らすことができます.
ユーザクラス実装例
#include <KinokoEventPieceScanner.hh>
class TMyEventPieceScanner: public TKinokoEventPieceScanner {
public:
typedef TKinokoEventPieceScanner::TStreamKey TStreamKey;
public:
TMyEventPieceScanner(void) {}
virtual ~TMyEventPieceScanner() {}
virtual int MaximumPieceSize();
virtual int Scan(TStreamKey StreamKey, TKinokoEventPiece& EventPiece);
};
int TMyEventPieceScanner::MaximumPieceSize()
{
return 256;
}
int TMyEventPieceScanner::Scan(TStreamKey StreamKey, TKinokoEventPiece& EventPiece)
{
if (DataChunk.Size() < 256) {
return 0;
}
EventPiece.PieceType = PieceType_Normal;
EvnetPiece.ChannelKey = DataChunk[0] & 0x0000ffff;
EventPiece.EventKey = DataChunk[1];
EventPiece.PieceFlag = 0;
return 256;
}
Horizontal Builder コンポーネントの作成
作成したユーザスキャナクラスを組み込んだ HorizontalBuilder コンポーネントを作成します.基本的にユーザコンポーネントの作成と同じ手順で,ユーザスキャナのインスタンス作成とその登録の2行だけがアプリケーション依存の部分となります.
/* MyHorizontalBuilder-kcom.cc */
#include <iostream>
#include "KcomProcess.hh"
#include "KinokoBuilderCom.hh"
#include "MyEventPiece.hh"
using namespace std;
int main(int argc, char** argv)
{
TMushArgumentList ArgumentList(argc, argv);
TKinokoHorizontalBuilderCom* Component = new TKinokoHorizontalBuilderCom();
TKcomProcess* ComProcess = new TKcomProcess(Component);
TKinokoEventPieceScanner* Scanner = new TMyEventPieceScanner();
Component->SetPieceScanner(Scanner);
try {
ComProcess->Start(ArgumentList);
}
catch (TKcomException &e) {
cerr << "ERROR: " << argv[0] << ": " << e << endl;
}
delete Scanner;
delete ComProcess;
delete Component;
return 0;
}
ついでに,単体テストのためのスタンドアロン版もつくっておきます.
/* my-horizontal-builder.hh */
#include <iostream>
#include "KinokoStandaloneBuilder.hh"
#include "MyEventPiece.hh"
using namespace std;
int main(int argc, char** argv)
{
TKinokoStandaloneHorizontalBuilder Builder(argc, argv);
TMoguraEventPieceScanner Scanner;
Builder.SetPieceScanner(&Scanner);
try {
return Builder.Start();
}
catch (TKinokoException &e) {
cerr << "ERROR: " << e << endl;
return -1;
}
}
Makefile は以下のようになります.
BINS = MyHorizontalBuilder-kcom my-horizontal-builder
OBJS = MyEventPiece.o
CXX=$(shell kinoko-config --cxx)
CXXFLAGS=$(shell kinoko-config --cxxflags)
LIBS=$(shell kinoko-config --libs)
MyHorizontalBuilder-kcom: MyHorizontalBuilder-kcom.o $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $@.o $(OBJS) $(LIBS)
my-horizontal-builder: my-horizontal-builder.o $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $@.o $(OBJS) $(LIBS)
.cc.o:
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFS) -c $<
設定ファイル
ビルダ設定ファイルは,Kinoko の基本スクリプトに builder エントリと set 文を追加したものになっています.builder エントリで対象ビルダを指定し,set 文でパラメータを設定します.ファイル拡張子には通常 kconf を使います(ファイル名 MyBuilder.kconf など).
Horizontal Builder
builder MoguraCrate03 {
int crate = 3;
set Type = "Horizontal";
set Name = "MoguraSorter" + dec(crate, 2);
set TargetDataSource = "Mogura" + dec(crate, 2);
list target_data_section;
for (int card = 1; card <= 20; card++) {
int key = 20 * (crate-1) + card;
target_data_section <+>= { key => "mogura" + dec(card,2) };
};
set TargetDataSection = target_data_section;
set BufferSize = BufferSize;
set StartDataSize = 0x1000;
//set StartPacketCycles = -1;
//set StartChannelList = 240 * (crate-1) + [12,240];
}
ここで,builder の次の MoguraCrate03 はビルダ名です.一つの設定ファイルに異なったビルダ名で複数の設定を記述することができます.
Vertical Builder
builder Mogura {
set Type = "Vertical";
set Name = "MoguraBuilder";
list target_data_source = { 0 => "MoguraTriggerSorter" };
for (int crate = 1; crate <= 6; crate++) {
target_data_source <+>= { crate => "MoguraSorter" + dec(crate,2) };
};
set TargetDataSource = target_data_source;
set BufferSize = BufferSize;
}
ここで,builder の次の Mogura はビルダ名です.一つの設定ファイルに異なったビルダ名で複数の設定を記述することができます.Horizontal Builder と Vertical Builder を混ぜても構いません.
パラメータ
set 文で設定できるパラメータは以下のとおりです.
共通パラメータ
- Type
- ビルダタイプを文字列で指定する.Horizontal もしくは Vertical
- Name
- ビルドされたデータのデータソース名を文字列で指定する
- BufferSize
- ビルダの作業バッファのサイズをバイト単位で指定する
- StartDataSize
- ビルディング開始条件にデータサイズを指定する場合のバイト単位のサイズ
- StartPacketCycle
- ビルディング開始条件にパケットサイクル数を指定する場合のサイクル数
- StartChannelList
- ビルディング開始条件にチャンネルテーブルを使用する場合の ChannelKey のリスト
- IsInputPreserved
- ビルダへ入力されたビルド対象データをビルド結果に加えて出力するかを指定する.true もしくは false (デフォルト).ビルド前のデータも合わせて記録したい場合に true を指定する.
Horizontal Builder のみのパラメータ
- TargetDataSource
- ビルド対象のデータソース名を文字列で指定する.
- TargetDataSection
- ビルド対象のデータセクション名を文字列リストで指定する.整数キーを指定するとその値がスキャナの Scan() メソッドの StreamKey パラメータとして渡される.
Vertical Builder のみのパラメータ
- TargetDataSource
- ビルド対象のデータソース名を文字列リストで指定する.整数キーを指定するとその値が StreamKey として使用され,同じイベントキーのデータに対してこの値の順にソートされる.
単体テスト
ビルダの開発には繰り返しテストが必要になることが多いので,KCOM を使わずに各コンポーネントのスタンドアロン版を使って開発作業を行うのが楽です.Horizontal Builder には,コンポーネント版と同時に作成したもの(my-horizontal-builder)を使います.Vertical Builder には,Kinoko 標準の kinoko-vertical-builder が利用できます.
Vertical Builder のテストには複数の Horizontal Builder の出力をバッファでマージしたものが必要ですが,これは kdfmerge コマンドで作ることができます.kdfmerge は全ての入力から1パケットずつ順に取り出し出力に書き出します.この動作はバッファの到着時間順出力と異なりますが,ここでの単体テストには十分役に立ちます.
% ./my-horizontal-builder --config=MyConfigFile.kconf --name=MyCrate0 mydata-crate0.kdf mydata-crate0-sorted.kdf
% ./my-horizontal-builder --config=MyConfigFile.kconf --name=MyCrate1 mydata-crate1.kdf built-crate1-sorted.kdf
% kdfmerge mydata-crate0-sorted.kdf mydata-crate1-sorted.kdf mydata-sorted.kdf
% kinoko-vertical-builder --config=MyConfigFile.kconf --name=MyBuilder mydata-sorted.kdf mydata-built.kdf
KCOM スクリプトの編集
ビルダコンポーネントは普通のパイプ型ストリームコンポーネントとして KCOM に組み込むことができます.つまり,KinokoTransporter コンポーネントとほぼ同じ記述をすればよいことになります(実際に,Transporter をプレースホルダーとしてシステムを作成しておき,あとからビルダコンポーネントに置き換えるのが便利です).Transporter コンポーネントとの違いは,宣言に加え,以下の KCOM イベントを加えることだけです.
accepts setConfig(string config_file, string builder_name);
accepts setReportInterval(int interval_sec);
これらの設定は,他のコンポーネント同様,construct() の直前に行います.
出力ファイルの読み方
ビルドされたデータを読むためには,TKinokoEventSegmentProcessor を継承して MyEventSegmentProcessor クラスを作り,TKinokoEventConsumer に登録します.EventPiece を読みたい場合は,TKinokoEventPieceProcessor を継承して MyEventPieceProcessor クラスを作り,Unpacker を経由して TKinokoEventConsumer に登録します.TKinokoEventConsumer は TKinokoDataConsumer から派生しているので,通常のデータプロセッサとして使用することができます.
以下のクラスは,kinoko/src/kernel/lib-domain/builder にあるイベントプロセッサの例です.
/* KinokoEventDumper.hh */
#ifndef __KinokoEventDumper_hh__
#define __KinokoEventDumper_hh__
#include <iostream>
#include "KinokoEventPiece.hh"
#include "KinokoBuilderProcessor.hh"
class TKinokoEventPieceDumper: public TKinokoEventPieceProcessor {
public:
TKinokoEventPieceDumper(std::ostream& Output);
virtual ~TKinokoEventPieceDumper();
virtual void ProcessEventBegin(TEventKey EventKey, int PieceType, int NumberOfPieces, size_t TotalSize);
virtual void ProcessEventEnd(void);
virtual int ProcessPiece(const TKinokoEventPiece& EventPiece);
protected:
std::ostream& _Output;
};
これを使うためのプログラムは以下のようになります.
#include <iostream>
#include "MushArgumentList.hh"
#include "KinokoEventProcessor.hh"
#include "KinokoEventSegmentPacker.hh"
#include "KinokoEventDumper.hh"
#include "KinokoStandaloneComponent.hh"
using namespace std;
int main(int argc, char** argv)
{
if (argc < 3) {
cerr << "Usage: " << argv[0] << " INPUT_FILE DATASOURCE_NAME" << endl;
return -1;
}
TMushArgumentList ArgumentList(argc, argv);
string InputDataSourceName = ArgumentList[1];
TKinokoEventPieceDumper PieceDumper(cout);
TKinokoEventSegmentUnpacker Unpacker(&PieceDumper);
TKinokoEventConsumer EventConsumer(InputDataSourceName, &Unpacker);
TKinokoStandaloneDataConsumer StandaloneConsumer(&EventConsumer, "EventDumper");
try {
StandaloneConsumer.Start(ArgumentList);
}
catch (TKinokoException &e) {
cerr << "ERROR: " << argv[0] << ": " << e << endl;
}
return 0;
}
# Makefile #
BINS = dump-event
CXX=$(shell kinoko-config --cxx)
CXXFLAGS=$(shell kinoko-config --cxxflags)
LIBS=$(shell kinoko-config --libs)
dump-event: dump-event.o
$(CXX) $(CXXFLAGS) -o $@ $@.o $(LIBS)
.cc.o:
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFS) -c $<
使い方は通常のデータプロセッサと同じです.
% ./dump-event built.kdf MyBuilder
最初のパラメータがデータファイル名で,次のパラメータが処理対象のデータソース名です.
この例の入力は EventSegment なので,Horizontal Builder と Vertical Builder の両方の出力に対して使用できます.
現状の問題
- データブロック幅が 32bit に限定されている.
- EventPiece が小さいとデータ量に対するパケット数が増える.
- Early Arraival のパケットが長時間処理されないとリングバッファを塞いでしまう可能性がある.将来はバッファを別にする.
- 不正タイムスタンプのデータが正常として渡されるとそれ以降の正常データがすべて Unagligned になる.後段で EarlyArrival となるのでバッファが一杯になってしまうかもしれない.可能な限り Scanner で検出して Corrupted とマークする.
- 一つの原因に対して大量のログメッセージが出力されてしまう場合がある.
Edited by: Enomoto Sanshiro