株式会社クリアコード > ククログ

ククログ


DebConf18でuscanの改善に関する発表をしました

はじめに

クリアコードの林です。

今回は、DebConf18で発表する機会があったのでその内容を紹介します。 今年のDebConf18は台湾の国立交通大学(National Chiao Tung University - NCTU)にて開催されました。*1

NCTU

桃園国際空港からはMRT*2 とHSR*3、台湾バス*4を乗り継いで行きました。

Taoyuan to Hsinchu

DebConf18の統計情報 *5によると、全体で309人が参加、そのうち日本からは27名が参加したようです。距離的にも近いということで参加者も多かったのではないでしょうか。

Rethinking of debian/watch rule

Xueshan Room X

発表資料は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

2018-08-06

リーダブルなコードを目指して:コードへのコメント(4)

まだギリギリ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が起動してからの経過時間をミリ秒単位で返す」関数でした。

INTERVALCommon.hで次のように定義されていました。

//フレームレート
#define INTERVAL 60
#define FPS 60

これをふまえると次のことがわかります。

  • フレームレートの単位時間は「Framerate::update()の呼び出し回数がINTERVAL(60)回」
    • 1秒とかではない。
  • フレームレートの単位時間がくる度に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だから)になっています。INTERVALFPSを統合できないかしら。こんな感じ?

//フレームを進める
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)を参照してください。

2018-08-13

ibus-daemonの--mem-profileオプションは非推奨になりました

はじめに

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オプションが非推奨になった経緯を紹介しました。

*1 GLibとGIOとでメモリー確保の仕組みに互換性がないため。

*2 Ubuntu 14.04ではGLib 2.40が採用されている。

2018-08-14

PostgreSQLの改良:拡張機能インデックスでのソート済みインデックススキャンのサポート

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);

次のようにテストデータを用意します。contentaのメッセージが100件、bcもそれぞれ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;

これに対して次のように検索します。contentbのメッセージを新しい順に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.cbuild_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.cget_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;
                    }

ということで、ここをもっと賢くするといい感じの実行計画を作れそうです。

まとめると次の通りです。

  • btreeがWEHREがあるケースでもないケースでもいい感じの実行計画になるのはget_relation_info()でbtreeが特別扱いされているから
  • PGroongaがWHEREがないケースではいい感じの実行計画になるのはソートの時は暗黙的に演算子として<が使われ、get_relation_info()では<があるときはソートできる扱いになっているから
  • PGroongaがWHEREがあるケースでいい感じの実行計画にならないのは、get_relation_info()のチェックで=が条件を満たせないから

テスト方法案

今の実装でbtree決め打ちになっているところがあるのは、組み込みのインデックスでamcanorderなインデックスがbtreeしかないからです。そのため、既存のインデックスを使って期待した動きになっているかを確認するテストを書くのは難しいです。

次の2つでテストするのがよいのではないかと考えています。

  • btreeに特化した処理を汎用性のある実装に置き換えてもPostgreSQLの既存のテストが動くようにする
    • 基本的にテストを追加しない
  • PGroongaにテストを追加し、汎用性のある実装に書き換えたPostgreSQLならPGroongaでも期待した実行計画になることを確認する

btreeだけでテストしていると本当に汎用的な実装になっているか確認漏れがでやすいので、PGroongaというbtreeではないインデックスでも確認するといいんじゃないかという案です。

まとめ

Zulipでよく使うパターンでPostgreSQLがいい感じの実行計画を作れないので作れるようにPostgreSQLを改良したいという話をまとめました。どういうケースで発生するかと、どのあたりから改良していけばいいかもまとめたので、一緒にやりたくなった人はPGroongaのチャットで連絡してください。一緒にPostgreSQLの開発に参加しましょう。

2018-08-17

fluent-plugin-elasticsearchのHTTPバックエンドを切り替えられるようにするには

はじめに

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について取り上げます。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の問題点

exconはHTTPのバックエンドライブラリとしては申し分がないのですが、keepaliveがデフォルトで有効にならないという問題がありました。 nginxなどのproxy配下ではkeepaliveが有効でないと接続が頻繁に切れ、効率的な転送が行えないという問題が報告されました。

elasticsearch-transportでFaradyアダプターのHTTPバックエンドを切り替えられるようにする

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バックエンドをぜひ試してみてください。

タグ: Fluentd
2018-08-21

fluent-plugin-elasticsearchのSnifferクラスについて

はじめに

fluent-plugin-elasticsearchはよく使われているプラグインの一つです。 このプラグインをメンテナンスするためには、Fluentdの知識だけでなく、Elasticsearchが今後どのようになっていくかも知っておく必要があります。 また、このプラグインはRed Hat社がメンテナンスしているOpenShiftのログコンポーネントの一部としても使われています。

elasticsearch-transportのSnifferクラスとは

elasticsearch-transportには定期的にクラスタの状況を監視するSnifferクラスがあります。このクラスではGET _nodes/httpというクラスタの状況を返答するAPIを叩いており、大抵の場合はこのAPIを叩いておけばElasticsearchクラスタの状況がfluent-plugin-elasticsearchが使っているelasticsearchクライアントに通知されます。 そのため、X-Packを用いない通常の使用方法では問題になりません。

k8sサービス化されたElasticsearchクラスタに接続する

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クラスを定義したりしてみてください。

タグ: Fluentd
2018-08-22

Debian Maintainerになるには

はじめに

クリアコードの林です。 今回は、Debianパッケージのメンテナンスに関わる上で、Debian Maintainerになるための方法を紹介します。

役割でみるDebianの開発に関わる人々

Debianには多くの開発者が関わっていますが、Debianパッケージをメンテナンスしている開発者の権限の観点で分類するといくつか種類があります。

  • Debian Developer(Debian開発者)
  • Debian Maintainer(Debianメンテナー)
  • Package Maintainer(パッケージメンテナー)

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になる価値があるかもしれません。

Debian Maintainerになるまでの流れ

2018年8月時点では、次のような流れになっています。

  • Debian Maintainerに専用のWebサイトから応募する
  • なぜDebian Maintainerになりたいのかをアピールする
  • Debian社会契約、フリーソフトウェアガイドライン、マシン使用ポリシーなどに同意する
  • GPG鍵のチェックを受ける
  • Debian Developerの推薦をもらう
  • レビューを受けて承認してもらう

なお、応募資格としてGPGの鍵に(最低1人、もっと多い方が望ましい)Debian Developerに署名してもらっておく必要があります。 もしまだであれば、お近くのDebian勉強会に参加するなどしてDebian Developerに署名してもらうとよいでしょう。 *2 定期的に開催されている勉強会がいくつかあります。

Debian Maintainerに専用のWebサイトから応募する

Debian Maintainerに応募するには専用のサイトである Debian New Members にアクセスします。

Debian New Membersウェブサイト

ページの中ほどにJoin the NM processというリンクがあるのでクリックします。

NMプロセスをはじめる

入力フォームから以下の情報を入力して送信します。

  • GPG鍵のフィンガープリント
  • 名前
  • 連絡先のメールアドレス
  • ユーザー名
  • 自己紹介文

上記のうち、ユーザー名というのはDebian Maintainerに応募する場合には必須ではありません。Debianプロジェクトの機材(サーバーとか)にアクセスするゲストアカウントとして使われるものだからです。将来的に必要そうであれば、予約することができるので申請しておいてもいいでしょう。

入力フォームを送信すると、確認用のメールがGPGで暗号化されて届きます。復号したテキストに含まれるURLにアクセスするとメールアドレスの確認が完了します。 ステータスがDebian Contributorになっているので、「request new status」というリンクをクリックします。

メールアドレス確認後の状態

ステータスが、Debian Contributorになっているのを、Debian Maintainerに変更して送信します。

新規ステータスを設定する

なぜDebian Maintainerになりたいのかをアピールする

Declaration of intentとしてなぜDebian Maintainerになろうとしているのかを書いてGPGで署名したうえで入力フォームから送ります。

なぜDebian Maintainerになりたいのか

Debian社会契約、フリーソフトウェアガイドライン、マシン使用ポリシーなどに同意する

内容を確認した上で、内容に同意する文言にGPGで署名したうえで入力フォームから送ります。

 SC DFSG DMUPに同意する

GPG鍵のチェックを受ける

とくにやることはありません。問題や疑念があれば指摘されるはずです。

Debian Developerの推薦をもらう

Debian DeveloperにDebian Maintainerとして適格者であることを示す推薦文を書いてもらう必要があります。 知り合いのDebian Developerがいればお願いするとよいでしょう。

レビューを受けて承認してもらう

ここまでくるとステータスが「Waiting for review」に変わり、管理者の承認待ちの状態になります。 人によって承認されるまでの期間はばらばらのようです。あせらず一ヶ月くらいをみておくといいかもしれません。

管理者の承認待ちの状態

まとめ

今回はDebian Maintainerになるための方法を紹介しました。 Debianのパッケージのメンテナンスに興味があったり、継続的にPackage Maintainerとして活動している人は、Debian Maintainerを目指してみるのはいかがでしょうか。

*1 Debianのプロジェクトリーダーは選挙で選ばれています。

*2 毎回確実にDebian Developerが参加しているとは限らないことに注意してください。

2018-08-30

YoctoのWeston上で日本語入力

はじめに

これまでにも何度か紹介していますが、クリアコードではGecko(Firefox)を組み込み機器向けに移植する取り組みを行っています。

その後、課題として残っていたWebRTCも無事に動作するようになり、主だった機能は主ターゲットであるRZ/G1M上で動作するようになっています。

今回は趣向を変えて、同環境上で少しだけ日本語入力を検証してみた経過を紹介します。

uimでの日本語入力の様子

Yoctoでの日本語入力事情

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の情報を切り貼りして公開してみました。

2018-08-31

«前月 最新記事 翌月»
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|