クリアコードの林です。
今回は、DebConf18で発表する機会があったのでその内容を紹介します。 今年のDebConf18は台湾の国立交通大学(National Chiao Tung University - NCTU)にて開催されました。*1
桃園国際空港からはMRT*2 とHSR*3、台湾バス*4を乗り継いで行きました。
DebConf18の統計情報 *5によると、全体で309人が参加、そのうち日本からは27名が参加したようです。距離的にも近いということで参加者も多かったのではないでしょうか。
発表資料はRabbit Slide Showにて公開しています。
Debianのパッケージでは、アップストリームの更新を検出するために uscan
というプログラムが使われています。uscan
の設定は debian/watch
という設定ファイルに記述します。
debian/watch
ファイルにはアップストリームのリリースファイルのアクセス先や、パッケージング内容に沿うようにファイル名を変換したりするなど、いくつかのルールを記述します。
このルールは正規表現を使って記述するのですが、場合によっては複雑なルールを記述しなければならないことがあります。
例えば、複雑なルールを記述しなければならない例の一つに、OSDN.netでリリースされているソフトウェアがあります。
version=4
opts="uversionmangle=s/-beta/~beta/;s/-rc/~rc/;s/-preview/~preview/, \
pagemangle=s%<osdn:file url="([^<]*)</osdn:file>%<a href="$1">$1</a>%g, \
downloadurlmangle=s%projects/sawarabi-fonts/downloads%frs/redir\.php?m=iij&f=sawarabi-fonts%g;s/xz\//xz/" \
https://osdn.net/projects/sawarabi-fonts/releases/rss \
https://osdn.net/projects/sawarabi-fonts/downloads/.*/sawarabi-mincho@ANY_VERSION@@ARCHIVE_EXT@/ debian uupdate
Debianパッケージ化をするときには、必須ではありませんが debian/watch
ファイルも用意してあると、その後のメンテナンスが楽になるのでおすすめです。
ただし、Debian Policyでは Optional
扱いです。*6
発表時には、パッケージングをするときに複雑になりがちな設定を簡単にできないかという観点で、debian/watch
の使われ方の傾向を調べつつ、フォーマットの改善案を提案してみました。
発表して終わりという性質のものではないので、引き続き、bugs.debian.orgにて議論していければいいなぁと思っています。
8/2の夜は海鮮が美味しいとされるおすすめのところでのカンファレンスディナーでした。DebConfのスポンサーのおかげですね。
今回は、DebConf18に参加して発表する機会があったので、その内容を紹介してみました。
映像記録ありのセッションに関しては、順次アーカイブからWebM形式の動画が参照できるようになっているようです。また、YoutubeのDebConf Videosチャンネルからも(過去のDebConfの映像を含め)閲覧することができるようになっています。 見逃したセッションがあればそちらを確認してみるのはいかがでしょうか。
*1 博愛キャンパスと光復キャンパスとがありますが会場であるNCTU MIRC(National Chiao Tung University Microelectronics and Information Research Center)は光復キャンパスにあります。
*2 桃園捷運(とうえんしょううん)。いわゆる地下鉄のこと。
*3 台湾高速鉄道。いわゆる新幹線のこと。
*4 前乗り、前降りというあまり日本人には馴染みのないスタイル。
*5 Debian SSOによるログインが必要です。
*6 https://www.debian.org/doc/debian-policy/ch-source.html#optional-upstream-source-location-debian-watch
まだギリギリ3週間に1回のペースの須藤です。このシリーズのファンができました。
リーダブルなコードを目指して:コードへのコメント(3)の続きです。前回はユーザーからの入力処理のところを読んでコメントしました。
リポジトリー: https://github.com/yu-chan/Mario
今回のコメントに関するやりとりをするissue: https://github.com/yu-chan/Mario/issues/4
今回はメインループ中のフレームレート関連の処理を見ていきましょう。
まずはメインループの中をおさらいします。
while(!InputInterface::isOn(KEY_INPUT_Q)) { //Qを押したら終了
if(ProcessMessage() != 0) {
break;
}
InputInterface::updateKey();
Framerate::instance()->update();
ClearDrawScreen();
//ゲーム開始
Sequence::Parent::instance()->update();
ScreenFlip();
Framerate::instance()->wait();
}
この中の以下の部分がフレームレート関連の処理のはずです。
Framerate::instance()->update();
Framerate::instance()->wait();
なぜかというとFramerate
クラスに属しているからです。
Framerate.h
は次のようになっています。
#ifndef INCLUDED_FRAMERATE_H
#define INCLUDED_FRAMERATE_H
class Framerate {
public:
static void create();
static void destroy();
static Framerate* instance();
void update();
void wait();
int cnt() const;
private:
Framerate();
~Framerate();
static Framerate* mInstance;
int mStartTime;
int mCnt;
float mFramerate;
};
#endif
以下の部分はシングルトンパターンを実現するためのコードなので今回は無視しましょう。
static void create();
static void destroy();
static Framerate* instance();
Framerate();
~Framerate();
static Framerate* mInstance;
ということで注目するのは以下の部分です。
class Framerate {
public:
void update();
void wait();
int cnt() const;
private:
int mStartTime;
int mCnt;
float mFramerate;
};
名前から想像するとそれぞれのメンバー関数が実現する機能は次の通りです。
update()
wait()
cnt()
うーん、update()
はメインループ中で毎回呼ばれているんですが、そこでフレームレートを毎回調整するとは思えないんですよねぇ。実装を見てみましょう。
//フレームを更新
void Framerate::update() {
if(mCnt == 0) {
mStartTime = GetNowCount();
}
if(mCnt == INTERVAL) {
int t = GetNowCount();
mFramerate = 1000.0f / ((t - mStartTime) / (float)INTERVAL);
mCnt = 0;
mStartTime = t;
}
mCnt++;
}
この中で初めて見るのはGetNowCount()
とINTERVAL
です。
GetNowCount()
はDXライブラリが提供している「Windowsが起動してからの経過時間をミリ秒単位で返す」関数でした。
INTERVAL
はCommon.h
で次のように定義されていました。
//フレームレート
#define INTERVAL 60
#define FPS 60
これをふまえると次のことがわかります。
Framerate::update()
の呼び出し回数がINTERVAL
(60)回」
mStartTime
の時刻をリセットしている。Framerate::update()
を呼び出す毎にmFramerate
を更新している。
mFramerate
を使っているところがなさそうなので、ここのコードは今は必要なさそう。mFramerate
は必要なさそうなので消した方がいいでしょう。必要ないコードがあると、読むときに「どうしてここにこんなコードがあるんだ。。。」と考えないといけなくなり、理解を妨げてしまいます。コードをバージョン管理していれば消したコードを戻すことができるので、1人で開発しているときでもバージョン管理しましょう。バージョン管理していれば安心してリーダブルなコードにするための変更を重ねていけます。実は、バージョン管理したほうがいいというのはリーダブルコードの解説にも書いています。
実装を読んでみた結果「フレームレートは更新していない(mFramerate
がいらなそう)だし、フレームレートの更新というかフレームを1回進めるのが目的っぽいのでupdate()
じゃない名前がよさそう」という気持ちになりました。なんていう名前がいいのかなぁ。tick()
とかかなぁ。フレームを1つ進めます、というイメージ。Node.JSにはprocess.nextTick()
というのがあるし。
ということで、こんな感じにするのはどうだろう。
//フレームを進める
void Framerate::tick() {
if((mCnt % INTERVAL) == 0) {
mStartTime = GetNowCount();
mCnt = 0;
}
mCnt++;
}
うーん、Framerate
というかFrame
の方がいいのかなぁ。
もやもやしたまま次に進みましょう。Framerate::update()
です。
//フレームが早かったら、早いぶんだけ待つ
void Framerate::wait() {
int t = GetNowCount() - mStartTime;
int w = mCnt * 1000 / FPS - t;
if(w > 0) {
Sleep(w);
}
}
こちらは名前から予想していた通りの実装です。ただ、FPS
の使い方がもやっとします。update()
では単位時間は秒ではなかったのにここでは単位時間は1秒(FPS
はFrame Per Secondだから)になっています。INTERVAL
とFPS
を統合できないかしら。こんな感じ?
//フレームを進める
void Framerate::tick() {
if((mCnt % FPS) == 0) {
mStartTime = GetNowCount();
mCnt = 0;
} else {
int t = GetNowCount() - mStartTime;
int w = mCnt * 1000.0 / FPS - t;
if(w > 0) {
Sleep(w);
}
}
mCnt++;
}
で、メインループでは最後にtick()
を呼ぶだけ。これで動かないかしら。
while(!InputInterface::isOn(KEY_INPUT_Q)) { //Qを押したら終了
if(ProcessMessage() != 0) {
break;
}
InputInterface::updateKey();
ClearDrawScreen();
//ゲーム開始
Sequence::Parent::instance()->update();
ScreenFlip();
Framerate::instance()->tick();
}
あと、変数名はこんな感じにしたいですね。
//フレームを進める
void Framerate::tick() {
if((mIndex % FPS) == 0) {
mStartTime = GetNowCount();
mIndex = 0;
} else {
int expectedElapsedTime = (1000.0 / FPS) * mIndex;
int elapsedTime = GetNowCount() - mStartTime;
int restTime = expectedElapsedTime - elapsedTime;
if(restTime > 0) {
Sleep(restTime);
}
}
mIndex++;
}
Framerate::cnt
は単にmCnt
を返しているだけでした。
int Framerate::cnt() const {
return mCnt;
}
そうだろうなぁという実装です。が、これを使っているコードはなさそうなので消したほうがよさそうに思いました。
リーダブルコードの解説を読んで「自分が書いたコードにコメントして欲しい」という連絡があったのでコメントしています。今回はメインループ内で使っているFramerate
を読んでコメントしました。次回はメインループの違う処理を読んでいきます。
「リーダブルなコードはどんなコードか」を一緒に考えていきたい人はぜひ一緒にコメントして考えていきましょう。なお、コメントするときは「悪いところ探しではない」、「自分お考えを押し付けることは大事ではない」点に注意しましょう。詳細はリーダブルなコードを目指して:コードへのコメント(1)を参照してください。
IBusの1.5.19からは、ibus-daemon
の--mem-profile
オプションは非推奨になりました。
今回はその経緯について紹介します。
IBusはGLibを外部ライブラリーの一つとして使っています。 このGLibですが、2.46からメモリプロファイリングに関する機能が動作しなくなりました。*1
これまでibus-daemon
では--mem-profile
というオプションを指定するとメモリプロファイリングの機能が有効になり、
SIGUSR2
を送りつけることでメモリ使用量に関するレポートを標準エラーに出力することができるようになっていました。
この機能を実現するために、ibus-daemon
ではGLibが提供する以下のAPIを使っていました。
しかし、GLib 2.46以降ではこれらの機能は非推奨となり使えません。
とはいえ、GLib 2.46というのはやや古く、これより前のものを採用しているのはUbuntu 14.04(trusty)あたりのリリースバージョンです。*2 そういった古めの環境でわざわざ最新のIBusを採用するというのは考えにくいため、IBusではGLib 2.46以前をサポートしないようにしました。
GLib 2.46以降の環境でibus-daemon
に--mem-profile
オプションを指定した場合には警告するようになっています。
今回はibus-daemon
の--mem-profile
オプションが非推奨になった経緯を紹介しました。
PostgreSQLの開発に参加する人が増えるといいなぁと思っている須藤です。
自分たちが使うケースでPostgreSQLがいい感じの実行計画を使ってくれないことがわかったので、PostgreSQLを改良したいなぁと思っています。べつにここで宣言しなくてもちまちま進めるのですが、もし、興味がある人(PostgreSQLの開発に参加したいけど題材が見つからなくて手を動かせずにいる人とか)がいればその人と一緒に取り組めるといいなぁと思ってまとめることにしました。そうすれば、PostgreSQLの開発に参加する人を増やす機会になるんじゃないかと思うからです。
ここにまとめた内容を読んで、一緒にやってみたいと思った人は連絡してください。うーん、どうやって連絡してもらうのがいいかな。そうだなぁ、PGroongaのチャットがあるのでそこから連絡してください。
まず、どんなケースでいい感じの実行計画を作ってくれないかを説明します。
次のようなメッセージを格納するテーブルがあります。各メッセージにはIDが振ってあり、このIDは増加する一方です。つまり、大きいほど新しいメッセージになります。
CREATE TABLE messages (
id serial,
content text
);
このメッセージに対してcontent
で絞り込み、新しい方から10件返すケースがいい感じの実行計画を作ってくれないケースです。
それでは、どんな実行計画を作って欲しいかを説明します。
次のようなマルチカラムインデックスを用意します。
CREATE INDEX messages_index ON messages (content, id);
次のようにテストデータを用意します。content
がa
のメッセージが100件、b
とc
もそれぞれ10000件のデータです。
INSERT INTO messages (content)
SELECT content FROM (SELECT 'a' AS content, generate_series(0, 9999)) AS values;
INSERT INTO messages (content)
SELECT content FROM (SELECT 'b' AS content, generate_series(0, 9999)) AS values;
INSERT INTO messages (content)
SELECT content FROM (SELECT 'c' AS content, generate_series(0, 9999)) AS values;
これに対して次のように検索します。content
がb
のメッセージを新しい順に10件取得するSELECT
です。
SELECT * FROM messages
WHERE content = 'b'
ORDER BY id DESC LIMIT 10;
いい感じの実行計画は次のようになります。インデックスがcontent='b'
で絞り込みつつid
で逆順にソートした結果を順に返します。この実行計画の場合はインデックスが10件返せばそれで必要な結果が得られるのでとてもいい感じです。
Limit (cost=0.29..26.33 rows=10 width=36)
-> Index Only Scan Backward using messages_index on messages (cost=0.29..390.91 rows=150 width=36)
Index Cond: (content = 'b'::text)
で、組み込みのbtreeインデックスを使うとこの実行計画になります。
しかし、拡張機能として追加したインデックスではこうなりません。拡張機能として実装されているインデックスの代表的なもの(?)はPGroongaです。PGroongaは全文検索ができるインデックスですが、btreeインデックスのようにソートした結果を返すこともできます。
たとえば、次のようにWHERE
がない単純なケースでは前述のいい感じの実行計画になります。インデックスでソートして順に10件取得して終わり、です。
DROP INDEX IF EXISTS messages_index;
CREATE INDEX messages_index ON messages USING PGroonga (id);
EXPLAIN
SELECT * FROM messages
ORDER BY id DESC LIMIT 10;
-- QUERY PLAN
-- -----------------------------------------------------------------------------------------------------
-- Limit (cost=0.00..0.28 rows=10 width=36)
-- -> Index Scan Backward using messages_index on messages (cost=0.00..832.00 rows=30000 width=36)
-- (2 rows)
しかし、WHERE
を追加するとそうなりません。
WEHRE
用にcontent
もインデックス対象にします。
DROP INDEX IF EXISTS messages_index;
CREATE INDEX messages_index ON messages
USING PGroonga (content, id);
このときの実行計画は次のようになります。
Limit (cost=511.24..511.27 rows=10 width=36)
-> Sort (cost=511.24..511.62 rows=150 width=36)
Sort Key: id DESC
-> Seq Scan on messages (cost=0.00..508.00 rows=150 width=36)
Filter: (content = 'b'::text)
シーケンシャルスキャンを無効にしてみましょう。
SET enable_seqscan = no;
それでもこうなります。インデックスでヒットするレコードを全部見つけてから、(インデックスは使わずに)ソートしています。
Limit (cost=511.28..511.30 rows=10 width=36)
-> Sort (cost=511.28..511.65 rows=150 width=36)
Sort Key: id DESC
-> Bitmap Heap Scan on messages (cost=0.04..508.04 rows=150 width=36)
Filter: (content = 'b'::text)
-> Bitmap Index Scan on messages_index (cost=0.00..0.00 rows=30000 width=0)
この実行計画ではヒット数が多くなるほど遅くなりやすいのであんまりいい感じではありません。
なお、この説明は実際のケースを単純化したものです。実際のケースはZulipというチャットツールでのケースです。ZulipはデータストアにPostgreSQLを使っています。オプションでPGroongaを使うこともできて、PGroongaを使うと高速にメッセージを探せます。Webサイトの検索やファイルサーバーの検索では検索クエリーにマッチする度合いで並び替えることが多いですが、チャットツールは時間順に並び替えます。そのため、全文検索してメッセージ投稿順に並び替える使い方になります。
このあたりのことをZulipの開発者と私で調べていたときのissueがzulip/zulip#9595で、結論がPostgreSQLがいい感じの実行計画を作ってくれないです。
Zulipはクリアコードの社内のチャットツールとして使っています。クリアコードはユーザーが自由に使えるソフトウェアを大事にしているのですが、Zulipを選んだのはZulipが自由に使えるソフトウェアだからです。自由に改造できるのでPGroongaサポートを追加して使っています。
PostgreSQLもユーザーが自由に改造できるソフトウェアなので今回のケースもいい感じにできるようにPostgreSQLを改良したいと思っています。
どうやって改良するとよさそうか少し調べたので記録を残しておきます。あとで自分で取り組むときにも役に立ちそうですし。
インデックススキャンを使った実行計画はsrc/backend/optimizer/path/indxpath.c
のbuild_index_paths()
で作られます。
ソート済みのインデックススキャンを使う実行計画は↓らへんで作られるのですが、index_is_ordered
が真にならないと作られません。で、真になるためにはindex->sortopfamily
が設定されないといけません。
index_is_ordered = (index->sortopfamily != NULL);
if (index_is_ordered && pathkeys_possibly_useful)
どういうときにindex-sortopfamily
が設定されるかというのはsrc/backend/optimizer/util/plancat.c
のget_relation_info()
を見るとわかります。
1つ目のパターンはbtreeを使っているときです。特別扱いです。
if (info->relam == BTREE_AM_OID)
{
/*
* If it's a btree index, we can use its opfamily OIDs
* directly as the sort ordering opfamily OIDs.
*/
Assert(amroutine->amcanorder);
info->sortopfamily = info->opfamily;
2つ目のパターンはamcanorder
(インデックスを登録するときに拡張機能で設定できる項目の1つで、このインデックスはソートできるよ!というのを示す設定)が設定されているときです。つまり、btree以外のインデックスでもソート済みのインデックススキャンをできるようにするためのところです。
else if (amroutine->amcanorder)
この中でさらにチェックがあります。使っている演算子がbtreeの比較演算子と同じ演算子か、というチェックです。同じならソート済みのインデックスキャンできる扱いにしています。
ltopr = get_opfamily_member(info->opfamily[i],
info->opcintype[i],
info->opcintype[i],
BTLessStrategyNumber);
if (OidIsValid(ltopr) &&
get_ordering_op_properties(ltopr,
&btopfamily,
&btopcintype,
&btstrategy) &&
btopcintype == info->opcintype[i] &&
btstrategy == BTLessStrategyNumber)
{
/* Successful mapping */
info->sortopfamily[i] = btopfamily;
}
ということで、ここをもっと賢くするといい感じの実行計画を作れそうです。
まとめると次の通りです。
WEHRE
があるケースでもないケースでもいい感じの実行計画になるのはget_relation_info()
でbtreeが特別扱いされているからWHERE
がないケースではいい感じの実行計画になるのはソートの時は暗黙的に演算子として<
が使われ、get_relation_info()
では<
があるときはソートできる扱いになっているからWHERE
があるケースでいい感じの実行計画にならないのは、get_relation_info()
のチェックで=
が条件を満たせないから今の実装でbtree決め打ちになっているところがあるのは、組み込みのインデックスでamcanorder
なインデックスがbtreeしかないからです。そのため、既存のインデックスを使って期待した動きになっているかを確認するテストを書くのは難しいです。
次の2つでテストするのがよいのではないかと考えています。
btreeだけでテストしていると本当に汎用的な実装になっているか確認漏れがでやすいので、PGroongaというbtreeではないインデックスでも確認するといいんじゃないかという案です。
Zulipでよく使うパターンでPostgreSQLがいい感じの実行計画を作れないので作れるようにPostgreSQLを改良したいという話をまとめました。どういうケースで発生するかと、どのあたりから改良していけばいいかもまとめたので、一緒にやりたくなった人はPGroongaのチャットで連絡してください。一緒にPostgreSQLの開発に参加しましょう。
fluent-plugin-elasticsearchはよく使われているプラグインの一つです。 このプラグインをメンテナンスするためには、Fluentdの知識だけでなく、Elasticsearchが今後どのようになっていくかも知っておく必要があります。 取り掛かりとして、fluent-plugin-elasticsearchの構造をまず軽く説明します。fluent-plugin-elasticsearchのElasticsearchのAPIリクエストは自前で実装しているのではなく、elasticsearch, elasticsearch-api, elasticsearch-transportというgemに依存しています。それぞれ、ElasticsearchのRubyクライアントライブラリをカプセル化して共通のインターフェースで使用できるようにgem化したもの、APIリクエストをgem化したもの、HTTPリクエストの方式をgem化したものです。
この中で、今回はelasticsearch-transport
について取り上げます。elasticsearch-transport
は複数のHTTPバックエンドを切り替えて使用することができます。
fluent-plugin-elasticsearchでは、これまで以下のようにHTTPリクエストのバックエンドライブラリとしてexcon
が固定で使われていました。
def client
@_es ||= begin
excon_options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
adapter_conf = lambda {|f| f.adapter :excon, excon_options } # f.adaptorに ':excon' 固定
transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(get_connection_options.merge(
options: {
reload_connections: @reload_connections,
reload_on_failure: @reload_on_failure,
resurrect_after: @resurrect_after,
retry_on_failure: 5,
logger: @transport_logger,
transport_options: {
headers: { 'Content-Type' => @content_type.to_s },
request: { timeout: @request_timeout },
ssl: { verify: @ssl_verify, ca_file: @ca_file, version: @ssl_version }
},
http: {
user: @user,
password: @password
}
}), &adapter_conf)
es = Elasticsearch::Client.new transport: transport
# ...
exconはHTTPのバックエンドライブラリとしては申し分がないのですが、keepaliveがデフォルトで有効にならないという問題がありました。 nginxなどのproxy配下ではkeepaliveが有効でないと接続が頻繁に切れ、効率的な転送が行えないという問題が報告されました。
elasticsearch-transport
はいくつかのHTTPバックエンドを使用することができます。その一つがFaradayアダプターを使用するものです。
Faradayはそれ単体ではHTTPを扱う統一的なインターフェースを提供するだけですが、実際のHTTPリクエストはHTTPを扱うライブラリに担当させます。
例えば、exconを使ってHTTPリクエストを出すには以下のようにします。
require 'excon'
client = Elasticsearch::Client.new(host: 'localhost', port: '9200') do |f|
f.response :logger
f.adapter :excon
end
この状態では、exconのみしかHTTPのバックエンドに使用することができません。
例えば、typhoeus
を使ってHTTPリクエストを投げるようにするには、
require 'typhoeus'
require 'typhoeus/adapters/faraday'
client = Elasticsearch::Client.new(host: 'localhost', port: '9200') do |f|
f.response :logger
f.adapter :typhoeus
end
のようにすると、HTTPのリクエストはTyphoeusを使って投げられるようになります。
実際のfluent-plugin-elasticsearchにHTTPバックエンドを変更できるようにしたパッチは以下の通りです。 (out_elasticsearch部分のみ示します。)
diff --git a/lib/fluent/plugin/out_elasticsearch.rb b/lib/fluent/plugin/out_elasticsearch.rb
index 42e1a16..cb5f1c0 100644
--- a/lib/fluent/plugin/out_elasticsearch.rb
+++ b/lib/fluent/plugin/out_elasticsearch.rb
@@ -107,6 +107,7 @@ elasticsearch gem v6.0.2 starts to use correct Content-Type. Please upgrade elas
see: https://github.com/elastic/elasticsearch-ruby/pull/514
EOC
config_param :include_index_in_url, :bool, :default => false
+ config_param :http_backend, :enum, list: [:excon, :typhoeus], :default => :excon
config_section :buffer do
config_set_default :@type, DEFAULT_BUFFER_TYPE
@@ -128,6 +129,7 @@ EOC
raise Fluent::ConfigError, "'tag' in chunk_keys is required." if not @chunk_key_tag
@time_parser = create_time_parser
+ @backend_options = backend_options
if @remove_keys
@remove_keys = @remove_keys.split(/\s*,\s*/)
@@ -207,6 +209,18 @@ EOC
end
end
+ def backend_options
+ case @http_backend
+ when :excon
+ { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
+ when :typhoeus
+ require 'typhoeus'
+ { sslkey: @client_key, sslcert: @client_cert, keypasswd: @client_key_pass }
+ end
+ rescue LoadError
+ raise Fluent::ConfigError, "You must install #{@http_backend} gem."
+ end
+
def detect_es_major_version
@_es_info ||= client.info
@_es_info["version"]["number"].to_i
@@ -257,8 +271,7 @@ EOC
def client
@_es ||= begin
- excon_options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
- adapter_conf = lambda {|f| f.adapter :excon, excon_options }
+ adapter_conf = lambda {|f| f.adapter @http_backend, @backend_options }
transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(get_connection_options.merge(
options: {
reload_connections: @reload_connections,
前述の通り、f.adapter
の部分へ切り替えたいHTTPバックエンドのgem名のシンボルを渡してあげれば良いことになります。
この記事では解説していませんが、バックエンドによってはTLSの設定方法に違いがある場合があるので新しいバックエンドを追加した際には無効なハッシュキーを渡さないように注意してください。
fluent-plugin-elasticsearchのHTTPバックエンドをexconだけではなくtyphoeusも扱えるように改修したお話を書きました。
記事で引用したパッチはfluent-plugin-elasticsearchのv2.11.4に取り込んでリリース済みです。
fluent-plugin-elasticsearchでkeepaliveが有効にならず、困っている場合はtyphoeus gemをインストールした後、http_backend typhoeus
の設定値を加えてkeepaliveが有効になるHTTPバックエンドをぜひ試してみてください。
fluent-plugin-elasticsearchはよく使われているプラグインの一つです。 このプラグインをメンテナンスするためには、Fluentdの知識だけでなく、Elasticsearchが今後どのようになっていくかも知っておく必要があります。 また、このプラグインはRed Hat社がメンテナンスしているOpenShiftのログコンポーネントの一部としても使われています。
elasticsearch-transportには定期的にクラスタの状況を監視するSnifferクラスがあります。このクラスではGET _nodes/http
というクラスタの状況を返答するAPIを叩いており、大抵の場合はこのAPIを叩いておけばElasticsearchクラスタの状況がfluent-plugin-elasticsearchが使っているelasticsearchクライアントに通知されます。
そのため、X-Packを用いない通常の使用方法では問題になりません。
k8sのサービスとはPodから生成したノードを一まとめにしたアクセス手段を提供します。k8sの世界観ではサービスのアクセス先は一定です。しかし、サービスを構成するノードの構成要素はある時は起動していますが、またある時は停止または破棄されています。このノード一つ一つにElasticsearchが立っていても通知速度よりも起動・破棄のサイクルが速ければGET _nodes/http
を使用しても欠点が目立つようになります。
そのため、k8sのサービス化されたElasticsearchクラスタには新たなSnifferクラスの実装が必要になります。
そこで、元々のSnifferクラスのhostsメソッドの実装を見てみると、以下のようになっています。
# Retrieves the node list from the Elasticsearch's
# [_Nodes Info API_](http://www.elasticsearch.org/guide/reference/api/admin-cluster-nodes-info/)
# and returns a normalized Array of information suitable for passing to transport.
#
# Shuffles the collection before returning it when the `randomize_hosts` option is set for transport.
#
# @return [Array<Hash>]
# @raise [SnifferTimeoutError]
#
def hosts
Timeout::timeout(timeout, SnifferTimeoutError) do
nodes = transport.perform_request('GET', '_nodes/http').body
hosts = nodes['nodes'].map do |id,info|
if info[PROTOCOL]
host, port = info[PROTOCOL]['publish_address'].split(':')
{ :id => id,
:name => info['name'],
:version => info['version'],
:host => host,
:port => port,
:roles => info['roles'],
:attributes => info['attributes'] }
end
end.compact
hosts.shuffle! if transport.options[:randomize_hosts]
hosts
end
end
nodes = transport.perform_request('GET', '_nodes/http').body
の行でElasticsearchクラスタの情報を取りに行き、取りに行った情報から再度クラスタの情報を再構築しています。
もし、接続先のURLやIPアドレスが固定であれば、以下のようなSnifferクラスを作成し、ホスト情報を使い回す振る舞いをさせた方が良いです。
require 'elasticsearch'
class Fluent::Plugin::ElasticsearchIdempotenceSniffer < Elasticsearch::Transport::Transport::Sniffer
def hosts
@transport.hosts
end
end
elasticsearchクライアントは独自Snifferを渡してそのクラスを元にクラスタ情報を再構築するようなカスタマイズをすることができます。
@sniffer = options[:sniffer_class] ? options[:sniffer_class].new(self) : Sniffer.new(self)
これらの変更をfluent-plugin-elasticsearchで扱うには以下のようにすると独自のSnifferクラスを用いてElasticsearchクラスタとやりとりできるようになります。
config_param :pipeline, :string, :default => nil
config_param :with_transporter_log, :bool, :default => false
config_param :emit_error_for_missing_id, :bool, :default => false
+ config_param :sniffer_class_name, :string, :default => nil
config_param :content_type, :enum, list: [:"application/json", :"application/x-ndjson"], :default => :"application/js
on",
:deprecated => <<EOC
elasticsearch gem v6.0.2 starts to use correct Content-Type. Please upgrade elasticserach gem and stop to use this option
.
#...
def client
@_es ||= begin
adapter_conf = lambda {|f| f.adapter @http_backend, @backend_options }
transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(get_connection_options.merge(
options: {
reload_connections: @reload_connections,
reload_on_failure: @reload_on_failure,
resurrect_after: @resurrect_after,
retry_on_failure: 5,
@@ -287,7 +300,8 @@ EOC
http: {
user: @user,
password: @password
- }
+ },
+ sniffer_class: @sniffer_class,
}), &adapter_conf)
es = Elasticsearch::Client.new transport: transport
fluent-plugin-elasticsearchのElasticsearchへのリクエストに関わるelasticsearch-transportのSnifferに関するお話を書きました。
記事と同様の働きをするパッチはfluent-plugin-elasticsearchのv2.11.5に取り込んでリリース済みです。
fluent-plugin-elasticsearchでk8sやnginxのプロキシを設置していて接続のリロード時に正常なElasticsearchクラスタの情報が取得できずに困っている場合はsniffer_class_name
の設定項目を初期値から変えてみたり、独自のSnifferクラスを定義したりしてみてください。
クリアコードの林です。 今回は、Debianパッケージのメンテナンスに関わる上で、Debian Maintainerになるための方法を紹介します。
Debianには多くの開発者が関わっていますが、Debianパッケージをメンテナンスしている開発者の権限の観点で分類するといくつか種類があります。
Debian Developerはその名が示すようにDebianの開発そのものに広く関わる権限をもっている人です。 たとえば、Debianパッケージをアップロードしたり、Debianプロジェクトに関わる投票権を保持しています。*1
Debian Maintainerは、Debian Developerにスポンサーをしてもらわずとも、特定のDebianパッケージに関してはアップロードすることを認められている人です。
Package MaintainerはDebianパッケージのメンテナンスに関わってはいますが、アップロードはDebian Developerにお願いしないといけない人のことです。
Debianパッケージをメンテナンスするのに、Debian DeveloperやDebian Maintainerであることは必須ではありません。 ただし、Debian Maintainerであれば、アップストリームが新しいバージョンをリリースしたときに割と早く対応しやすいということはいえます。 これは、Package Maintainerと違ってDebianパッケージを自分でアップロードするところまでできる、というところによります。 都合よくメンテナンスしているパッケージのスポンサーをしてくれる人がいればよいのですが、そうでない場合もあり得ます。 継続的にメンテナンスしているパッケージがあるのであれば、Debian Maintainerになる価値があるかもしれません。
2018年8月時点では、次のような流れになっています。
なお、応募資格としてGPGの鍵に(最低1人、もっと多い方が望ましい)Debian Developerに署名してもらっておく必要があります。 もしまだであれば、お近くのDebian勉強会に参加するなどしてDebian Developerに署名してもらうとよいでしょう。 *2 定期的に開催されている勉強会がいくつかあります。
Debian Maintainerに応募するには専用のサイトである Debian New Members にアクセスします。
ページの中ほどにJoin the NM processというリンクがあるのでクリックします。
入力フォームから以下の情報を入力して送信します。
上記のうち、ユーザー名というのはDebian Maintainerに応募する場合には必須ではありません。Debianプロジェクトの機材(サーバーとか)にアクセスするゲストアカウントとして使われるものだからです。将来的に必要そうであれば、予約することができるので申請しておいてもいいでしょう。
入力フォームを送信すると、確認用のメールがGPGで暗号化されて届きます。復号したテキストに含まれるURLにアクセスするとメールアドレスの確認が完了します。 ステータスがDebian Contributorになっているので、「request new status」というリンクをクリックします。
ステータスが、Debian Contributorになっているのを、Debian Maintainerに変更して送信します。
Declaration of intentとしてなぜDebian Maintainerになろうとしているのかを書いてGPGで署名したうえで入力フォームから送ります。
内容を確認した上で、内容に同意する文言にGPGで署名したうえで入力フォームから送ります。
とくにやることはありません。問題や疑念があれば指摘されるはずです。
Debian DeveloperにDebian Maintainerとして適格者であることを示す推薦文を書いてもらう必要があります。 知り合いのDebian Developerがいればお願いするとよいでしょう。
ここまでくるとステータスが「Waiting for review」に変わり、管理者の承認待ちの状態になります。 人によって承認されるまでの期間はばらばらのようです。あせらず一ヶ月くらいをみておくといいかもしれません。
今回はDebian Maintainerになるための方法を紹介しました。 Debianのパッケージのメンテナンスに興味があったり、継続的にPackage Maintainerとして活動している人は、Debian Maintainerを目指してみるのはいかがでしょうか。
これまでにも何度か紹介していますが、クリアコードではGecko(Firefox)を組み込み機器向けに移植する取り組みを行っています。
その後、課題として残っていたWebRTCも無事に動作するようになり、主だった機能は主ターゲットであるRZ/G1M上で動作するようになっています。
今回は趣向を変えて、同環境上で少しだけ日本語入力を検証してみた経過を紹介します。
Yoctoの主要なレイヤを概観してみたところ、IMフレームワークとしてはuim、日本語変換エンジンとしてはAnthyが見つかりました。
逆に、これ以外のIMフレームワークや日本語変換エンジンのレシピを見つけることはできませんでしたので、今回はこれを使用してみます(uim-skkでも良いと思いますが、一般向けにはやや紹介しづらいので、今回はAnthyのみを対象とします)。
さて、ウィンドウシステムがX11であれば、おそらく上記レシピをそのままビルドするだけで使用できるでしょう。ですが、今回の対象はYoctoのcore-image-westonであり、ウィンドウシステムはWayland/Westonです。uimにWaylandサポートが入ったのは1.8.7からで、上記レシピは1.8.6ですから、恐らくこれを普通にビルドするだけでは日本語入力できるようにはならないでしょう。実際に試してみましたが、やはりアプリケーションがクラッシュして起動できないという結果となりました。
上記仮説が正しければ、単にuimを最新版に上げるだけでWeston上でも動作するでしょう。ですが、実際にuimのバージョンを上げてビルドを試してみたところ、同レシピに含まれるパッチがそのままでは当たらないなどの問題が発生しました。そちらを修正するのも難しくはないでしょうが、まずは手っ取り早く動くか動かないかを確認したかったため、uim-1.5.6のまま最低限のパッチを最新のuimからバックポートしてみることにしました。以下がmeta-openembeddedに対するパッチです。
commit f3b0e042986a83a767a967ec352c731037693d98
Author: Takuro Ashie <ashie@clear-code.com>
Date: Fri Aug 17 13:16:21 2018 +0900
uim: First aid to work with Wayland
diff --git a/meta-oe/recipes-support/uim/uim/0001-Fix-the-problem-that-the-candidate-window-is-not-sho.patch b/meta-oe/recipes-support/uim/uim/0001-Fix-the-problem-that-the-candidate-window-is-not-sho.patch
new file mode 100644
index 0000000..6ebeb21
--- /dev/null
+++ b/meta-oe/recipes-support/uim/uim/0001-Fix-the-problem-that-the-candidate-window-is-not-sho.patch
@@ -0,0 +1,26 @@
+From f266ff2b59bc3b0cd732c62683a1df9672114c1d Mon Sep 17 00:00:00 2001
+From: Konosuke Watanabe <konosuke@media.mit.edu>
+Date: Sat, 20 Feb 2016 12:30:35 +0900
+Subject: [PATCH] Fix the problem that the candidate window is not shown in
+ GTK3 environment.
+
+---
+ gtk2/immodule/uim-cand-win-gtk.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/gtk2/immodule/uim-cand-win-gtk.c b/gtk2/immodule/uim-cand-win-gtk.c
+index 1bfe759c..41590d06 100644
+--- a/gtk2/immodule/uim-cand-win-gtk.c
++++ b/gtk2/immodule/uim-cand-win-gtk.c
+@@ -225,7 +225,7 @@ uim_cand_win_gtk_init (UIMCandWinGtk *cwin)
+
+ gtk_widget_set_size_request(cwin->num_label, DEFAULT_MIN_WINDOW_WIDTH, -1);
+ gtk_window_set_default_size(GTK_WINDOW(cwin), DEFAULT_MIN_WINDOW_WIDTH, -1);
+- gtk_window_set_resizable(GTK_WINDOW(cwin), FALSE);
++ gtk_window_set_resizable(GTK_WINDOW(cwin), TRUE);
+ }
+
+ static void
+--
+2.17.1
+
diff --git a/meta-oe/recipes-support/uim/uim/0001-gtk3-support-Wayland-backend.patch b/meta-oe/recipes-support/uim/uim/0001-gtk3-support-Wayland-backend.patch
new file mode 100644
index 0000000..e40caeb
--- /dev/null
+++ b/meta-oe/recipes-support/uim/uim/0001-gtk3-support-Wayland-backend.patch
@@ -0,0 +1,71 @@
+From 06558e571967f3cb989bdb550d1dea05247cc21d Mon Sep 17 00:00:00 2001
+From: Kouhei Sutou <kou@clear-code.com>
+Date: Sat, 30 Dec 2017 21:15:50 +0900
+Subject: [PATCH] gtk3: support Wayland backend
+
+GitHub: fix #71
+
+Debian: 810739
+
+Reported by Thibaut Girka. Thanks!!!
+---
+ gtk2/immodule/gtk-im-uim.c | 16 ++++++++++++++++
+ gtk2/immodule/key-util-gtk.c | 8 +++++++-
+ 2 files changed, 23 insertions(+), 1 deletion(-)
+
+diff --git a/gtk2/immodule/gtk-im-uim.c b/gtk2/immodule/gtk-im-uim.c
+index ac2918ce..066e5f5b 100644
+--- a/gtk2/immodule/gtk-im-uim.c
++++ b/gtk2/immodule/gtk-im-uim.c
+@@ -535,6 +535,22 @@ layout_candwin(IMUIMContext *uic)
+ gdk_window_get_geometry(uic->win, &x, &y, &width, &height, &depth);
+ #endif
+ gdk_window_get_origin(uic->win, &x, &y);
++ {
++ GtkWindow *window = NULL;
++ GdkWindow *gdk_window = uic->win;
++ while (gdk_window) {
++ gpointer user_data;
++ gdk_window_get_user_data(gdk_window, &user_data);
++ if (user_data && GTK_IS_WINDOW(user_data)) {
++ window = user_data;
++ break;
++ }
++ gdk_window = gdk_window_get_parent(gdk_window);
++ }
++ if (window) {
++ gtk_window_set_transient_for(GTK_WINDOW(uic->cwin), window);
++ }
++ }
+ uim_cand_win_gtk_layout(uic->cwin, x, y, width, height);
+ }
+ }
+diff --git a/gtk2/immodule/key-util-gtk.c b/gtk2/immodule/key-util-gtk.c
+index 27abd834..bd029e73 100644
+--- a/gtk2/immodule/key-util-gtk.c
++++ b/gtk2/immodule/key-util-gtk.c
+@@ -319,6 +319,7 @@ im_uim_init_modifier_keys()
+ #ifdef GDK_WINDOWING_X11
+ int i, k = 0;
+ int min_keycode, max_keycode, keysyms_per_keycode = 0;
++ GdkDisplay *gdk_display;
+ Display *display;
+ GSList *mod1_list, *mod2_list, *mod3_list, *mod4_list, *mod5_list;
+ XModifierKeymap *map;
+@@ -329,7 +330,12 @@ im_uim_init_modifier_keys()
+
+ mod1_list = mod2_list = mod3_list = mod4_list = mod5_list = NULL;
+
+- display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
++ gdk_display = gdk_display_get_default();
++ if (!GDK_IS_X11_DISPLAY(gdk_display)) {
++ /* TODO: We may need to something for Wayland. */
++ return;
++ }
++ display = GDK_DISPLAY_XDISPLAY(gdk_display);
+ map = XGetModifierMapping(display);
+ XDisplayKeycodes(display, &min_keycode, &max_keycode);
+ sym = XGetKeyboardMapping(display, min_keycode,
+--
+2.17.1
+
diff --git a/meta-oe/recipes-support/uim/uim_1.8.6.bb b/meta-oe/recipes-support/uim/uim_1.8.6.bb
index 271718e..e7241f1 100644
--- a/meta-oe/recipes-support/uim/uim_1.8.6.bb
+++ b/meta-oe/recipes-support/uim/uim_1.8.6.bb
@@ -4,17 +4,19 @@ LICENSE = "BSD-3-Clause & LGPLv2+"
LIC_FILES_CHKSUM = "file://COPYING;md5=32463fd29aa303fb2360faeeae17256b"
SECTION = "inputmethods"
-SRC_URI = "http://uim.googlecode.com/files/uim-${PV}.tar.bz2"
+SRC_URI = "https://github.com/uim/uim/releases/download/uim-${PV}/uim-${PV}.tar.bz2"
SRC_URI_append_class-target = " file://uim-module-manager.patch \
file://0001-fix-bug-for-cross-compile.patch \
file://0001-Add-support-for-aarch64.patch \
+ file://0001-gtk3-support-Wayland-backend.patch \
+ file://0001-Fix-the-problem-that-the-candidate-window-is-not-sho.patch \
"
SRC_URI[md5sum] = "ecea4c597bab1fd4ba98ea84edcece59"
SRC_URI[sha256sum] = "7b1ea803c73f3478917166f04f67cce6e45ad7ea5ab6df99b948c17eb1cb235f"
DEPENDS = "anthy fontconfig libxft libxt glib-2.0 ncurses intltool"
-DEPENDS_append_class-target = " intltool-native gtk+ gtk+3 uim-native takao-fonts"
+DEPENDS_append_class-target = " intltool-native gtk+3 uim-native takao-fonts"
RDEPENDS_uim = "libuim0 libedit"
RDEPENDS_uim-anthy = "takao-fonts anthy libanthy0 glibc-utils glibc-gconv-euc-jp"
@@ -31,6 +33,7 @@ EXTRA_OECONF += "--disable-emacs \
--without-canna \
--without-mana \
--without-eb \
+ --without-gtk2 \
"
CONFIGUREOPTS_remove_class-target = "--disable-silent-rules"
なお、当時のmeta-openembeddedのレシピではuimのソースコードをダウンロードすることが出来なったので、上記パッチにはその修正も含まれています。この点については既にOpenEmbeddedプロジェクトに報告済みで、修正が取り込まれています。
ベースのブートイメージのビルド方法はこれまでと同様ですので割愛します。 これにuimを追加するには、以下の設定をconf/local.confに追加してcore-image-westonを再作成します。
IMAGE_INSTALL_append = " uim uim-common uim-gtk3 uim-anthy "
Anthyを既定のIMとするには、以下の内容で設定ファイル~/.uimを作成します。
(define default-im-name 'anthy)
firefoxを起動する際に、環境変数GTK_IM_MODULE=uim
をセットすることでuimが使えるようになります。
$ WAYLAND_DISPLAY=wayland-0 GTK_IM_MODULE=uim firefox
YoctoのWeston上での日本語入力について、現在の検証状況を紹介しました。こんな記事を書いている暇があったらとっととアップストリームのuimをバージョンアップしてしまいたいところではありますが、すぐには作業に取りかかれないため、まずは社内Wikiの情報を切り貼りして公開してみました。