クリアコードではFluentdというソフトウェアの開発に参加しています。Fluentdはログ収集や収集したデータの分配・集約などを行うソフトウェアです。
また、Fluentdにはプラグインのしくみがあり、たくさんのFluentdのプラグインが開発されています。
同じようなログ収集や収集したデータ分配・集約のソフトウェアとして、Apache Flume NGというものもあります。
Flume NGも同じく、その仕組みがあります。Flume NGのSourceにはThriftプロトコルを利用したものが存在し、これを利用してThriftが利用できる言語であれば気軽にFlume NGへの取り込み(Source)ができる仕組みとなっています。
これらのソフトウェアで相互にデータの分配・集約をする目的で開発されたFluentdのプラグインがfluent-plugin-flumeです。しかし、Apache Flume側の開発が進んでいた事によりfluent-plguin-flumeを追従させる必要がありました。
この記事ではfluent-plugin-flumeを最新のFlume NGのthriftプロトコルにどう対応させたかを解説します。
fluent-plugin-flumeが何故今のバージョンのflumeで使用出来なくなっていたかを解説するにはFlumeの歴史をひも解く必要があります。
Flumeはソフトウェアの歴史上、2つのバージョン系統があります。
一つはFlume Legacyと呼ばれる0.9.0までのもの。もうひとつは1.0以上のNG (Next Generation) と呼ばれるものです。
まずは、Flume LegacyからFlume NGでThriftプロトコル周りがどのように変わったかという事を見ていきます。
Flume Legacy時代のThriftプロトコルは次のようになっていました。
namespace java com.cloudera.flume.handlers.thrift
typedef i64 Timestamp
enum Priority {
FATAL = 0,
ERROR = 1,
WARN = 2,
INFO = 3,
DEBUG = 4,
TRACE = 5
}
enum EventStatus {
ACK = 0,
COMMITED = 1,
ERR = 2
}
struct ThriftFlumeEvent {
1: Timestamp timestamp,
2: Priority priority,
3: binary body,
4: i64 nanos,
5: string host,
6: map<string,binary> fieldss
}
# Instead of using thrift's serialization, we just assume the contents are serialized already.
struct RawEvent {
1: binary raw
}
service ThriftFlumeEventServer {
oneway void append( 1:ThriftFlumeEvent evt ),
oneway void rawAppend( 1:RawEvent evt),
EventStatus ackedAppend( 1: ThriftFlumeEvent evt ),
void close(),
}
このプロトコルはThriftFlumeEventServerというserviceが付いています。また、ackedAppendなるものが見えます。これらによりFlume Legacyの時代はFlumeへの取り込み(Source)、Flumeからの送出(Sink)がどちらもThriftプロトコルで実装できるようになっていました。
ではFlume NGのThriftプロトコルの定義を見てみます。
namespace java org.apache.flume.thrift
struct ThriftFlumeEvent {
1: required map <string, string> headers,
2: required binary body,
}
enum Status {
OK,
FAILED,
ERROR,
UNKNOWN
}
service ThriftSourceProtocol {
Status append(1: ThriftFlumeEvent event),
Status appendBatch(1: list<ThriftFlumeEvent> events),
}
おや?かなりThriftプロトコルでできることが限られているように見えます。
例えば、append
や appendBatch
はThriftプロトコルの上に載せることは可能ですが、ackedAppend
がありません。
このメソッドはfluent-plugin-flumeのin_flumeで使われていたものです:https://github.com/fluent/fluent-plugin-flume/blob/c1d3d3308618e3d447d67f4f78678de8ee64793e/lib/fluent/plugin/in_flume.rb#L146
また、Thriftプロトコルの名称が変わり、ThriftSourceProtocol
となっていることから、このThriftプロトコルはFlume NGへのSource専用ということが読み取れます。
やっと本題です。FluentdからFlumeへレコードを送信するにはこの新しい ThriftSourceProtocol
に対応しないといけないことがわかりました。
そうして対応させたものをPull Requestしました。このPull Requestにより、Fluentd
からFlume NGへのSourceが動くようになりました。また、Flume LegacyとFlume NGとでは ThriftFlumeEvent
の構造体が変わっていることにも注意が必要でした。
また、残念ながらFlume NGからFluentdへの送出はThriftプロトコルに載せることができなかったので、先のPull Requestでは制限事項としました。
この変更はfluent-plugin-flume 0.1.2に取り込まれました。FluentdからFlumeに送れるようになったfluent-plugin-flumeを是非試してみてください!
クリアコードではFluentdというソフトウェアの開発に参加しています。Fluentdはログ収集や収集したデータの分配・集約などを行うソフトウェアです。
また、fluent-loggerと呼ばれるFluentdへデータを転送出来る実装が各言語で公開されています。
同じようなログ収集や収集したデータ分配・集約のソフトウェアとして、Apache Flume NGというものもあります。
Apache Flume NGにはデータの入力とデータの出力のAPIが公開されており、プラグインを実装することが可能です。 Apache Flume NGの場合はそれぞれ入力がSource、出力がSinkと呼ばれています。
FluentdのようにApache Flume NGに対してもサードパーティがプラグインを公開しています。 ただし、サードパーティのプラグインはJVMで動き、Flume NGのjarが公開しているAPIを使用できる言語なら好きなものを使う事ができます。 中にはFlumeの実装に使われているJavaではなくScalaで実装されたFlume NGプラグインも存在します。
FlumeのSinkでは次のメソッドを実装することが必要です。
public void start(); // @Override org.apache.flume.AbstractSink's start()
public void stop(); // @Override org.apache.flume.AbstractSink's stop()
public void process(); // @Override org.apache.flume.Sink's process()
public void configure(); // Implement org.apache.flume.conf.Configurable
そのほか、トランザクションを使う時にはorg.apache.flume.Transaction
をimportし、チャンネル毎のトランザクションを発行する必要があったり、org.apache.flume.Sink
にある Status
定数を用いて、トランザクションが成功すれば READY
、失敗であれば BACKOFF
とするように書きます。
Apache Flume NGはJavaで実装されている上に、特段他の言語を採用する理由が見当たらなかったのでJavaで実装しました。また、ビルドツールにはGradleを使用しました。 リポジトリはこちらです。
Javaのfluent-loggerにはfluent-logger-javaが存在しますが、より速いと謳っているfluencyをfluent-loggerとして用いる事にしました。
FlumeのSinkとして作成をしてみると以下の利点がある事が分かりました。
という点です。このため、設定によっては安全に転送する事が出来ます。
ほとんどREADMEに書いていますが、Flume NGの${FLUME_HOME}/lib 以下に gradle shadowJar
を実行して作成されたflume-ng-fluentd-sink-${version}-all.jarを配置し、confを書くだけです。
0.0.1のリリースタグを打ってあるので、このv0.0.1 リリースページからflume-ng-fluentd-sink-0.0.1-all.jarをFlume NGの${FLUME_HOME}/lib 以下に配置することでも使う事ができます。
Flume NGを使っていて、Fluentdに送れる機能が欲しいなぁという方にはぜひ試して頂きたいです。そうでなくてもJavaでこうした方が良いですよ、というものでもPRして下されば取り込みますのでよろしくお願いします。
FirefoxのJavaScript実行エンジンであるSpiderMonkeyでは、ECMAScriptの仕様にはないSpiderMonkey固有の拡張機能をいくつか利用できますが、その中の1つとして__noSuchMethod__
があります。
これはRubyなどでいう「メソッドミッシング」を実現するための仕組みで、あるオブジェクトに__noSuchMethod__
という名前のメソッドを定義しておくと、呼び出されたメソッドが存在しなかった時に代わりに__noSuchMethod__
メソッドが呼ばれるというものです。
具体的には以下のように使います。
// インスタンスに直接定義する場合
var object = {};
object.__noSuchMethod__ = function(name, args) {
alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
object.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]
// クラスの一部として定義する場合
function MyClass() {
}
MyClass.prototype.__noSuchMethod__ = function(name, args) {
alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
var instance = new MyClass();
instance.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]
こういった機能はいわゆるメタプログラミングにあたり、普段の開発で頻繁に利用する物ではありませんが、フレームワーク的な物を開発する場面では重宝します。 Ruby on Railsなどの「設定より規約」というルールでよく見られる「このような名前でパラメータを与えておけば自動的に、与えた名前に基づくこのような名前のメソッドが利用可能になる」という仕組みを、より自然な形で実現させられます。
ただ、前述の通りこれはSpiderMonkey固有の機能なので他のJSエンジンでは利用できませんし、SpiderMonkeyにおいてもFirefox 44で廃止されました。
代わりとして、より汎用的なProxy
を使う事が推奨されています。
とはいうものの、Proxy
は__noSuchMethod__
とは全く違う様式を取っており、しかも機能が豊富なので、単純に「__noSuchMethod__
と同じ事だけをしたい」という場合にどうすればよいのか分かりにくいです。
また、__noSuchMethod__
を使っていた箇所の設計を見直してProxy
を適切に使うようにするとしても、それなりに規模が大きいコードの場合、フレームワーク的な基盤部分の設計を大きく変えてしまうと変更の影響範囲が大きくなって後が大変です。
結論を述べると、先のような例であれば、以下のようにしてProxy
で__noSuchMethod__
を代替できます。
// インスタンスに直接定義する場合
var object = {};
object.__noSuchMethod__ = function(name, args) {
alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
// 追加箇所:ここから
object = (function(source) {
var cache = {};
return new Proxy(source, {
get: function(target, name) {
if (name in target)
return target[name];
return cache[name] || cache[name] = function(...args) {
return target.__noSuchMethod__.call(this, name, args);
};
}
});
})(object);
// 追加箇所:ここまで
object.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]
// クラスの一部として定義する場合
function MyClass() {
// 追加箇所:ここから
var cache = {};
return new Proxy(this, {
get: function(target, name) {
if (name in target)
return target[name];
return cache[name] || cache[name] = function(...args) {
return target.__noSuchMethod__.call(this, name, args);
};
}
});
// 追加箇所:ここまで
}
MyClass.prototype.__noSuchMethod__ = function(name, args) {
alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
var instance = new MyClass();
instance.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]
これまで__noSuchMethod__
を使っていたコードに対して、例で「追加箇所」と記した部分を付け加えることで、同等の結果を得られるようになります。
ただし、厳密には全く同一というわけではありません。
__noSuchMethod__
はメソッド呼び出しのみが対象になりますが、Proxy
を使用したバージョンでは「未定義のプロパティにアクセスされたら、__noSuchMethod__
を実行する関数オブジェクトを返す」という形になっているので、undefined
が返される事を期待して単純にvar hasProperty = !!instance.something;
のようにした場合にも影響が出てしまいます。
この例であれば、"something" in instance
のような判別方法を使うという風に、「未定義のプロパティにアクセスするとundefined
だけでなく関数が返ってくる事もある」という前提で対象オブジェクトを処理するように気をつける必要がありますので、ご注意下さい。
また、例をよく読むと分かりますが、最初からProxy
を使って書くのであればさらに無駄のない書き方もできます。
ここではあくまで、これまで__noSuchMethod__
を使っていた既存のそれなりの規模があるコードに対して、最小限の変更で同様の結果を得られるようにするという目的に特化しているために、このようになっています。
このようなアドホックな対応で済ませるよりは、Proxy
のAPI設計に即した使い方になるようにコードの設計を見直す方が基本的には望ましいと言えますので、実行に移すかどうかはメリットとデメリットをよく考えてから判断しましょう。
Groonga Advent Calendar 2015の6日目の記事です。5日目は@KitaitiMakotoさんのDroongaをインストールするItamaeレシピでした。
先日の11月29日(いい肉の日)にGroonga Meatup 2015が開催されました。その中で「Groonga族2015」と題してGroonga族の概要・最新情報・今後のことを話しました。
関連リンク:
内容は大きくわけて次の3つです。
なお、Groonga族とは次の条件に当てはまるプロダクトのことです。
たとえば、次のプロダクトはGroonga族です。
最初の「概要」の部分はプロダクトを紹介するときに使える情報が入っているので、Groonga族を知らない人はこの部分を活用してください。
続く「最新情報」の部分は今年のGroonga族の変更をキャッチアップできていない人にオススメの内容になっています。
最後の「今後」の部分はこれからのGroonga族の動向が気になる人にオススメの内容になっています。
Groonga族に興味のある方はぜひ確認してください。
イベントの他の発表についてはGroonga公式ブログのGroonga Meatup 2015開催を参照してください。
隔週金曜日に「Groongaで学ぶ全文検索」というイベントを開催しています。予習・復習なしで全文検索・Groongaの理解を深めることを大事にしているイベントです。全文検索・Groongaに興味のある方はご活用ください。
開催日は新着イベント一覧ページで確認できます。次回の開催日は2015年12月18日です。定員が埋まっていた場合でもキャンセル待ちに登録していれば定員を増やせないか検討することができるので、参加したい方は定員が埋まっているかどうかに関わらず登録してください。
12月11日から13日の3日間RubyKaigi 2015が開催されます。なんと、もう今週です。3日目は学生は無料で入れるということなので、学生の方は3日目の16:40からの咳さんのActor, Thread and meを聞きに行くといいです。最後の「and me」がかっこいいですね。
クリアコードは例年RubyKaigiのスポンサーをしています。去年のRubyKaigi 2014のスポンサーに引き続き、RubyKaigi 2015もスポンサーになりました。
また、須藤がスピーカーとして2日目の12月12日の10:45からのセッションで話します。Rubyのテスティングフレームワークの歴史+αについて話す予定です。
例年、クリアコードにとってRubyKaigiは数少ない露出の機会となっています。RubyKaigiに参加したことで正社員への応募やインターンシップへの応募がきます。昨年はRubyKaigi関連の機会を活用し、正社員が2名増えました。今回の機会もぜひ活かしたいところなので、クリアコードを知っていて応援したいという方は、会期中、まわりの人にクリアコードを紹介してください。ご協力よろしくお願いします。
リーダブルコードワークショップのチラシとGroonga族のステッカーも配布する予定なので、こちらも応援よろしくお願いします。
Groonga Advent Calendar 2015の10日目の記事です。
全文検索エンジンGroongaにはプラグイン機構があり、本体に手を加えずに独自の機能を追加することができます*1。
この記事では、主にプラグインを作ったことがない人向けに、プラグインの概要と作り方について説明します。また、プラグインの雛形を作成するツールの紹介もあるので、その部分はプラグインを作ったことがある人にとっても有用かもしれません。
GroongaはC言語のライブラリーとして使うためのAPIを提供していて、プラグインからもAPIを使うことができます。主な機能はAPI経由で使うことができるため、プラグインでも多くのことが可能です。
APIでどんなことができるかは、リファレンスマニュアルやヘッダーファイルで調べることができます。
使いたい機能がAPIとして公開されていない場合、メーリングリストなどで相談すれば公開してもらえるかもしれません。
プラグインの使い方を簡単に説明します。プラグインを使うには以下の手順が必要です。
Groonga本体に付属している場合は不要ですが、本体とは別のパッケージになっていたり、サードバーティのものについてはインストールが必要です。パッケージが提供されているものはAPTやYumなどでインストールできますが、パッケージが提供されていないものについてはソースコードをビルドしてインストールする必要があります。
プラグインの機能を使う前に、登録する必要があります。登録はplugin_register
コマンド(Groonga 5.0.0まではregister
コマンド)で実行します。詳細はリファレンスマニュアルをご覧ください。
プラグインはいろいろな形式で作成することができます。例えば、コマンドや関数、トークナイザー、ノーマライザーなどです。詳細はリファレンスマニュアルのそれぞれの章をご覧ください。
プラグインを作り始める場合、最初はコマンドから始めるのがわかりやすいと思います。
プラグインには、Groonga本体に同梱されているものと、Groongaプロジェクトやサードパーティが別パッケージとして提供しているものがあります。
Groonga本体に同梱されているものについては、こちらでソースコードを見ることができます*2。
Groonga本体に同梱されていないプラグインには以下のようなものがあります。
ここから、実際にプラグインを作成しながら説明します。サンプルとして、「hi」と出力するだけのコマンドを作成してみます。
一から作成するのは大変なので、雛形作成ツールを作成しました。まだ実験的なツールなので、今後、機能や名前が変わるかもしれません。
RubyGems.orgで公開しているので、以下のコマンドでインストールできます。
gem install grnplum
以下のコマンドで雛形を作成できます。プラグイン名はgreetにします。
$ grnplum new greet
実行すると、以下のファイルが生成されます。
$ grnplum new greet
create groonga-plugin-greet/.gitignore
create groonga-plugin-greet/LICENSE
create groonga-plugin-greet/Makefile.am
create groonga-plugin-greet/README.md
create groonga-plugin-greet/autogen.sh
create groonga-plugin-greet/configure.ac
create groonga-plugin-greet/greet/Makefile.am
create groonga-plugin-greet/greet/greet.c
create groonga-plugin-greet/greet/sources.am
create groonga-plugin-greet/test/Makefile.am
create groonga-plugin-greet/test/run-test.sh
create groonga-plugin-greet/test/suite/register.expected
create groonga-plugin-greet/test/suite/register.test
以下の手順でビルドしてテストを実行できます。詳細は上記で生成されたREADME.mdにも書かれています。--prefix
にはGroogaのインストール先を指定してください。
$ cd groonga-plugin-greet
$ ./autogen.sh
$ ./configure --prefix=/tmp/local
$ make
$ test/run-test.sh
ビルド用のファイルが多いですが、プラグインの本体はgreet/greet.c
になります。簡単に解説します。
#include <groonga/plugin.h>
プラグイン向けのヘッダーファイルを読み込んでいます。ライブラリー用のAPIに加えて、プラグイン用の関数も使えるようになります。
static grn_obj *
command_greet(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
grn_ctx_output_bool(ctx, GRN_FALSE);
return NULL;
}
コマンドの本体です。実装時はこの部分を修正していきます。
grn_rc
GRN_PLUGIN_INIT(grn_ctx *ctx)
{
return GRN_SUCCESS;
}
プラグインの初期化処理です。何か初期化処理が必要な場合はここに追記します。
grn_rc
GRN_PLUGIN_REGISTER(grn_ctx *ctx)
{
grn_expr_var vars[0];
grn_plugin_command_create(ctx, "greet", -1, command_greet, 0, vars);
return ctx->rc;
}
ここで、command_greet
関数をgreet
コマンドに登録しています。
grn_rc
GRN_PLUGIN_FIN(grn_ctx *ctx)
{
return GRN_SUCCESS;
}
プラグインの終了処理です。何か後始末が必要な場合はここに追記します。
では、コマンド本体を以下のように修正します。
static grn_obj *
command_greet(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
grn_ctx_output_cstr(ctx, "hi");
return NULL;
}
grn_ctx_output_cstr()
で、文字列を出力することができます。差分は以下です。
static grn_obj *
command_greet(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
- grn_ctx_output_output(ctx, GRN_FALSE);
+ grn_ctx_output_cstr(ctx, "hi");
return NULL;
}
それでは、インストールして実行してみましょう。
$ make
$ make install
$ groonga
> plugin_register greet/greet
[[0,1449653297.6119,0.000138044357299805],true]
> greet
[[0,1449653298.65187,3.62396240234375e-05],"hi"]
上記のように、hi
と出力されれば成功です。
なお、make uninstall
コマンドでアンインストールできます。
また、テスト方法については、Groongaのプラグインを多数作成されている村上さんがGroongaコマンドのテストツールgrntestの使い方 - CreateFieldという記事をまとめてくれているので、そちらも合わせてご覧ください。
Groongaのプラグインを作ったことがない人向けに、Groongaのプラグインを「どう」作るのか簡単に説明しました。Groongaの機能拡張に興味がある方は参考にしてみてください。
RubyKaigi 2015の2日目(2015年12月12日)にThe history of testing framework in RubyというタイトルでRubyのテスティングフレームワークの歴史を紹介しました。
関連リンク:
この発表の内容は次の2つにわかれています。
歴史はRubyのテスティングフレームワークの歴史(2014年版)をベースにそろそろリリースされるRuby 2.3までの情報を追加したものになっています。RubyUnitについてもっと知りたい方はRubyUnitの開発者である助田さんが書いた[Ruby] RubyKaigi 2015(2日目): 遠回りするかなも読むとよいです。
Test::Unit APIについてはスライドの72ページ以降を参照してください。ざっくりというとTest::Unit APIは普通のRubyのコードを書くようにテストを書けることを大事にしている、ということを具体例を交えて説明しています。
ただし、フィクスチャーのAPIはそれほどRubyらしくありません。いつものRubyでは次のようにブロックを使って後処理の詳細を隠蔽できます。
File.open("README") do |file|
# ...
end # ← ブロックが終わったら開いたファイルを確実に閉じる
しかし、Test::Unit APIでは次のように別のメソッドで後処理を書かなければいけません。いつものRubyの書き方をできていないということです。
def setup
@file = File.open("README")
end
def teardown
@file.close
end
この点についてFastly, Travis CI, GitHub 共催 RubyKaigi 前々夜祭で田中 哲さんから指摘がありました。そこで、発表までの間にtest-unit(発表を聞いた人なら区別をつけられるでしょうがgemのやつです)のmasterでは次のように使えるようにしました。
def setup
File.open("README") do |file|
@file = file
yield # ここでtest_readが動く
end # テストが終わったらファイルを閉じる
end
def test_read
assert_equal("XXX", @file.read)
end
ただ、@file = file
の部分がもやっとします。ふだんのRubyのコードではインスタンス変数に設定する使い方をしないからです。Test::Unit APIではインスタンス変数にテストで使うオブジェクトを設定するのは普通なので、テストコードという文脈では普通ですが、ふだんのRubyのコードという文脈では不自然なのです。
別の案として次のようなAPIも検討しましたが、やはりインスタンス変数に設定するところがもやっとします。(この書き方は既存のtest-unitですでに使えますが、非公開のAPIを使うことになるので実験用のコード以外では使わないでください。)
def run_test
File.open("README") do |file|
@file = file
super
end
end
なお、環境変数を設定するような次のケースではsetup
の中でyield
する書き方で違和感はありません。インスタンス変数を使わないからです。
def setup
original_lang = ENV["LANG"]
begin
ENV["LANG"] = "C"
yield
ensure
ENV["LANG"] = original_lang
end
end
File.open
のようにブロックにデータを渡す使い方のときは、次のようにテストの中で実行した方がよいのかもしれません。
def test_read
File.open("README") do |file|
assert_equal("XXX", file.read)
end
end
APIについてなにかアイディアがある方はtest-unit/test-unitのissueにコメントをお願いします。RubyKaigi 2015中には次のフィードバックをもらいました。
setup
ではなくaround
など違う名前のAPIにした方がよいのではないかsetup
なのか。テスト毎にインスタンスを作るならinitialize
でやるのが普通ではないかtest_read
)が引数を受け取るようにしたらよいのではないか(そうすればインスタンス変数に設定する必要はなくなる)yield
にオブジェクトを渡したら自動でインスタンス変数に設定するのはどうか違う名前のAPIはよいかもしれませんが、around
はなじまないのが悩ましいところです。Test::Unitの既存のフィクスチャーのAPIはsetup
/teardown
というように役割を名前にしているのでaround
というように順序を名前にするのはなじまないのです。RSpecのようにbefore
/after
というように順序を名前にしているならなじむのかもしれません。
なお、setup
の中で後処理も実行されることがもやっとするのでsetup
ではない名前がよさそう、ということであれば、そこは心配しなくてもよいです。File.open
のように本処理を名前にしているメソッドがブロック付きで呼ばれたら後処理をするようになることは普通のRubyのコードだからです。setup
も本処理の名前です。
initialize
でやるのはよいかもしれませんが、後処理をするタイミングを作れないのが悩ましいところです。普通のRubyのコードではFile.open {...}
のように処理の範囲を明示しないときはGCのタイミングで後処理が動きます。テストでは他のテストに影響を残さないように、テストが終わったら確実に後処理を実行しておきたいです。
テストメソッドが引数を受け取ると複数のセットアップ処理を指定したときに破綻しそうな点が悩ましいところです。
yield
にオブジェクトを渡すと自動でインスタンス変数を設定するのは可読性が悪くなりそうなところが悩ましいところです。
ちなみに、setup
でyield
しても動くようにする仕組みは次の擬似コードのように実装しています。
def run_test
block_is_called = false
setup do
block_is_called = true
test_read
end
test_read unless block_is_called
end
先日RubyKaigi 2015にスピーカー・スポンサーとして参加予定でオススメした通り、咳さんのActor, Thread and meが非常に興味深かったです。
bartender(咳フリークはこの名前を聞くとDivを思い出すでしょう)はRubyWorld Conference 2014で話した同期っぽいAPIの実装と同じようなものでした。いまだに着手していませんが、これをベースに複数の通信を並行して進めるときにそれっぽく書けそうなAPIがあるとよさそうと検討していたのでした。
クリアコードがシルバースポンサーとして応援したイベント「RubyKaigi 2015」での発表「The history of testing framework in Ruby」の内容を簡単に紹介しました。また、オススメの咳さんの発表についても少し触れました。
今回、スポンサーとして参加した成果はまだわかりません。配布物としてリーダブルコードワークショップのチラシとGroonga族のステッカーを置いておきました。参加者が帰った後に残った分を回収しましたが、どちらもほぼなくなっていました。興味があった方はぜひお問い合わせください。
Firefoxの導入時の要件として、クラッシュ時のレポートを送信しないようにするという設定を行う事があります。 この設定が意図通りに反映されているかどうかを確認するために、Firefoxが実際にクラッシュした時の様子を観察したい場合があります。
Firefoxには既知で且つ未修正のクラッシュバグがいくつかあるため、それを突くようなコードを実行すればFirefoxをクラッシュさせる事ができます。 しかし、クラッシュバグは再現性が低い物もありますし、そもそも新しいバージョンのFirefoxではクラッシュしないように修正されていることも多いです。 安定してFirefoxをクラッシュさせる方法を把握しておけば、Firefoxをクラッシュさせるための方法をその都度あれこれ調べなくても済みます。
Firefoxにはjs-ctypesという、JavaScriptからC言語製の共有ライブラリの機能を直接呼び出す機能が含まれており、これを使えば、比較的簡単にFirefoxをクラッシュさせられます。 例えば以下のようなコードを実行すれば、使用中のメモリ領域を強制的に解放してメモリ破壊を引き起こし、Firefoxをクラッシュさせる事ができます。
(function(){
Components.utils.import('resource://gre/modules/ctypes.jsm');
// Firefoxの構成ファイルの1つであるnss3.dll(nss3.so)をオープンする。
var library = ctypes.open(ctypes.libraryName('nss3'));
// 指定したアドレスのメモリを解放する関数を取得する。
var PR_Free = library.declare(
'PR_Free',
ctypes.default_abi,
ctypes.void_t,
ctypes.voidptr_t
);
// 適当なアドレスを指定してメモリを解放する。
var ptr = new ctypes.voidptr_t(1);
PR_Free(ptr);
})();
上記のスクリプトを実行するには、js-ctypesを利用する権限(chrome権限)が必要です。 「スクラッチパッド」を使って実行する場合は以下の手順となります。
about:config
を開き、devtools.chrome.enabled
の値をtrue
に設定する。なお、メモリ破壊を引き起こしてFirefoxをクラッシュさせると履歴や設定などのデータが破損する場合があります。 この方法を実際に試す際は、データが壊れてもよいテスト用のプロファイルを使う事を強くお勧めします。
FirefoxやThunderbirdには、内部で発生したエラーの情報やデバッグ用の情報を表示するためのコンソールが内蔵されています。 FirefoxでもThunderbirdでも、「Ctrl-Shift-J」というキーボードショートカットでこのコンソールを開く事ができます。
FirefoxのエラーコンソールはWeb開発ツール由来の物となっており、表示された内容を簡単に選択してクリップボードにコピーできます。 しかしながら、Thunderbirdのエラーコンソールは設計上の都合から、1つ1つのログをコピーする事しかできず、また表示可能なログの最大件数も250件に制限されています。 大量のメッセージが発生する状況ではすぐにログが流れてしまって、有効な情報を収集することができません。
以下は、この制限を実証するための「300件のエラーを報告する」というサンプルコードです。
for (var i = 1; i <= 300; i++) { Components.utils.reportError('Error '+i); };
これをエラーコンソール内の「コード」欄にコピー&ペーストして「コードを評価」ボタンをクリックして実行すると、それまで表示されていたログや新規のログの300件のうち最初の50件までは消えてしまい、「Error 51」から「Error 300」までの250件だけが見えるという結果になることが分かります。 実際の運用上で問題が発生している時に、エラーコンソール内をすさまじい速度でログが流れていってしまうと、原因の切り分けのための調査自体もはかどりません。
幸い、Thunderbirdのエラーコンソールでは任意のコードを実行できます。 この機能を使えば、エラーコンソール自身の最大表示件数を変える事ができます。
以下のコードをエラーコンソール内の「コード」欄にコピー&ペーストし、「コードを評価」ボタンをクリックして実行すると、ログの最大件数が99999件に拡大されます。
Components.utils.import('resource://gre/modules/Services.jsm');var box = Services.wm.getMostRecentWindow('global:console').document.getElementById('ConsoleBox'); var descriptor = { value: 99999 }; Object.defineProperty(box, 'limit', descriptor); Object.defineProperty(box, 'fieldMaxLength', descriptor);
実際に、続けて以下のようなコードを実行してみると、それまでのログと併せてすべてのログが表示されることを確認できます。
for (var i = 1; i <= 300; i++) { Components.utils.reportError('New Error '+i); };
この方法は、エラーコンソールを開いた時点より後に出力されるログに対してしか利用できません。 (エラーコンソールを開く前に大量にログが出ている場合、それらは既に制限を超えて流れてしまっているために、この方法では見る事ができません。) とはいえ、エラーコンソールを開いた後の任意のタイミングで再現試験を行える状況であれば十分に有用でしょう。
なお、この変更は「エラーコンソール」のウィンドウを閉じるまでの間のみ有効で、エラーコンソールを開き直したりすると最初の状態(最大250件のみ表示)に戻ります。
また、以下のコードを実行すれば、その時点でコンソールに表示されているすべてのログをまとめてクリップボードにコピーできます。
Components.utils.import('resource://gre/modules/Services.jsm');var text=[];for (var row of Services.wm.getMostRecentWindow('global:console').document.getElementById('ConsoleBox').mConsoleRowBox.children) { if (row.boxObject.height > 0) { text.push(row.toString()); } }; Components.classes['@mozilla.org/widget/clipboardhelper;1'].getService(Components.interfaces.nsIClipboardHelper).copyString(text.join('\n---\n'));
これなら、ログの件数が多い場合でも容易にログを保存することができます。 アドオンのエラー情報を作者に伝える場合や、バグトラッキングシステムに問題を報告する時などにご活用下さい。
このククログでも既に何度か触れていますが、Firefox 44以降ではアドオンのインストール用パッケージ(XPIパッケージ)について、Mozillaによるデジタル署名が施されていない物はインストールできないようになります。
この変更は、一般の利用者にとっては使い勝手の面であまり変化はありませんが、アドオンを開発・公開する側にとっては大きな影響があります。 その1つとして、アドオンをMozilla Add-ons以外の方法で頒布する場合に必要なステップが1つ増えるという事が挙げられます。
というのも、Mozillaによるデジタル署名が施された状態のXPIパッケージを入手するには、Mozilla Add-onsのWebサイトにXPIパッケージをアップロードし、場合によってはエディターによる承認を得た上で、署名済みのXPIパッケージを改めてダウンロードしなくてはなりません。 この作業自体は簡単なのですが、「Webサイトを訪問して、ログインして、フォームを使ってファイルを送信して、署名済みのファイルをダウンロードする」という手作業が必要でした。 このような定型的な作業を毎回人の手で行うのは非効率であるのみならず、ミスの発生原因になりうるので、可能であれば自動化したいところです。
このような状況を受けて、Mozilla Add-onsのWebサイト側にSigning APIという物が実装され、上記の作業のある程度の自動化が可能になりました。
上記リンク先の記事では具体的な手順が案内されていますが、「このようにすればコマンド一発で可能」というものではないため、自分でやるには若干ハードルが高いです。 そのため、アドオンSDKに含まれるコマンドラインツールのjpmに署名APIを使ってXPIに署名するためのサブコマンドが追加されました。 SDKを使ってアドオンを開発する場合は、これを使うことで署名の申請までを自動化することができます。
それとは別のものとして、SDKを使わないでアドオンを開発する場合向けに、XPIパッケージの署名申請を支援するBashスクリプトを作成しました。 この記事では、jpmではなくこのスクリプトを用いてXPIパッケージへのデジタル署名を申請する手順を解説します。
※2015年12月21日追記:上記のjpm
コマンドは、SDKを導入しなくてもjpm
というNPMパッケージ単体の導入により利用可能になるとのことです。
よって、基本的にはアドオンのXPIパッケージへの署名にはjpm
のサブコマンドsign
を使うことをお薦めします。
以下で紹介するスクリプトは、jpm sign
ではカバーできない範囲のニーズを満たす必要がある場合にのみ使うようにして下さい。
署名申請の支援スクリプトは、Ubuntu 14.04LTS上で動作を確認済みです。
スクリプトそのものはBashスクリプトですが、APIへのアクセスにcurl
を使用します。
また、Node.js用のJWTライブラリを使うため、nodejs
コマンドおよびnpm
コマンドが利用可能になっている必要があります。
以下の説明では、例としてUbuntuでの操作手順を示します。 他の環境で使う場合は、環境依存の記述を適宜読み替えて下さい。
署名申請にの支援スクリプトは複数に分かれているため、スクリプトは単体でダウンロードするのではなく、スクリプト群一式を作業ディレクトリに保存するようにして下さい。 例えば以下の要領です。
$ mkdir -p /tmp/signxpi
$ cd /tmp/signxpi
$ git clone https://github.com/piroor/makexpi.git
署名の申請を自動化するにあたっては、JSON Web Token(JWT)用のAPIキーが必要です。 これは、Mozilla Add-onsの開発者センターのAPIキー管理画面で生成できます。 本記事執筆時点では用途別の複数APIキーの使い分けは考慮されていないようで、生成できるAPIキーは一種類のみとなっています。 既にAPIキーを生成済みの場合、APIキーを生成し直すと古いAPIキーは無効になる模様ですのでご注意下さい。
APIキーは、「JWT発行者(JWT issuer)」と「JWT秘密鍵(JWT secret)」の2つの文字列のペアとして生成されます。 APIキーを準備できたら、次のステップに進みます。
署名申請を行うスクリプトは、リポジトリ内にあるsign_xpi.sh
です。
以下のようにオプションを指定して実行します。
$ makexpi/sign_xpi.sh -p "XPIファイルのパス" \
-o "署名済みファイルの保存先ディレクトリのパス" \
-k "JWT発行者" \
-s "JWT秘密鍵" \
-d
例えば、XPIファイルがカレントディレクトリにある「myaddon.xpi」で、署名済みのファイルを同じくカレントディレクトリに保存するのであれば、以下のようになります。
$ JWT_ISSUER=user:xxxxxx:xxx
$ JWT_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
$ makexpi/sign_xpi.sh -p "./myaddon.xpi" \
-o "./" \
-k "$JWT_ISSUER" \
-s "$JWT_SECRET" \
-d
The file will be uploaded for signing.
The file will be uploaded for signing.
という出力を得られたでしょうか?
実は、この時点ではまだファイルは送信されていません。
-d
はいわゆるdry runを行うためのオプションで、このオプションが指定された場合、実際のファイル送信は行われないようになっています。
dry runに成功したら、改めて-d
オプションを外し、実際にファイルを署名申請します。
(慣れてきたら、dry runでの確認はスキップして構いません。)
$ makexpi/sign_xpi.sh -p "./myaddon.xpi" \
-o "./" \
-k "$JWT_ISSUER" \
-s "$JWT_SECRET"
申請に成功し、署名済みのファイルが得られた場合、署名済みのファイルがカレントディレクトリに保存されます。
なお、署名APIは、「アドオンの新しいバージョンをアップロードするためのAPI」という形をとっているため、この時点で、署名申請を行ったバージョンは当該アドオンの新バージョンとして登録されることになります。 この時、当該バージョンは詳細情報が入力されていない状態になりますので、公開用の(listedな)アドオンの場合は、別途バージョン一覧から詳細情報を入力する必要があります。
レビュー待ちなどの理由で署名済みのファイルをすぐには入手できなかった場合、Not signed yet. You must retry downloading after signed.
というメッセージが表示されてエラーとなります。
この場合、レビューが完了して署名済みファイルをダウンロードできるようになった段階で再挑戦する必要があります。
また、署名済みのファイルを紛失してしまった場合にも、再度ダウンロードしなくてはなりません。
このようなケースでは、download_signed_xpi.sh
という別のスクリプトを使います。
$ makexpi/download_signed_xpi.sh -i "アドオンの内部ID" \
-v "署名済みファイルをダウンロードするバージョン" \
-o "署名済みファイルの保存先ディレクトリのパス" \
-k "JWT発行者" \
-s "JWT秘密鍵"
例えば、アドオンのIDが「myaddon@example.com」で、「バージョン1.5」の署名済みのファイルをカレントディレクトリに保存するのであれば、以下のようになります。
$ JWT_ISSUER=user:xxxxxx:xxx
$ JWT_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
$ makexpi/download_signed_xpi.sh -i "myaddon@example.com" \
-v "1.5" \
-o "./" \
-k "$JWT_ISSUER" \
-s "$JWT_SECRET"
De-coupling Reviews from Signing Unlisted Add-ons | Mozilla Add-ons Blog(私的翻訳)によると、非公開(unlisted)のアドオンについては、人力のレビューを経ずに即座に署名済みのXPIパッケージを入手できるようになっている模様です。
ただ、人力レビューが必要な場合であっても、この署名APIによって「新バージョンリリース時のファイルのアップロード」までは自動化できます。 署名義務化の発表以降、アドオンの開発や提供、検証にあたって手間が増えた部分はいくつかありますが、こういった部分でささやかながらリリースの自動化をしやすくなってくれたことは、素直に有り難いことだと言えるでしょう。
先日、Firefoxアドオンの署名義務化に伴っていくつかのアドオンのMozilla Add-onsのWebサイト上で配布しないように切り替えた旨をお伝えしましたが、その際、Cert Importer(証明書インポータ)だけは、その特性上サイドローディング形式でのインストールが可能な権限を設定して貰えない(本審査が棄却されていた)ために、Firefox 44以降のバージョンでは一切利用できない状態のままとなっておりました。
この度、本件について進展がありましたので、この場にてご報告いたします。
Firefoxに新たに認証局証明書をインポートする際には、その証明書を信頼するかどうかの確認が表示されます。 Cert Importer 1.3までのバージョンは、この確認をスキップして証明書を完全に自動的にインポートする設計となっていましたが、この「確認をスキップする」という点が問題視されていたがために、本アドオンは本審査が棄却される結果となっていました。
これを受けて、Cert Importer 1.4では当該機能を削除し、証明書のインポート時にはユーザ自身の手による明示的な許可の操作を必要とするように仕様を変更しました。 既に、署名済みのXPIパッケージはCert ImporterのGitHubリポジトリのリリース一覧のページからダウンロードできるようになっています。
利便性の点では後退する結果となりましたが、悪意ある攻撃者が攻撃用の証明書を完全に自動でインストールするというような悪用のされ方を防ぐためには避けられない変更ですので、何卒ご容赦下さい。
Firefox 44以降のバージョンではサイドローディングであるかどうかに関わらず、アドオンにはMozillaによる署名が必須となります。 アドオンのXPIパッケージへの署名申請を自動化する仕組みは既に用意されており、大抵の場合はこれにより署名申請の手間を軽減できるため、大きな混乱は無いと予想されますが、それでも解決不可能な問題が残ります。 それは、社外秘の情報を含むアドオンの取り扱いについてです。
というのも、現在アドオンに有効な署名を施すことができるのはMozilla Corporationのみであるため、署名を施してもらうためにはアドオンのインストール用パッケージをMozilla Corporationに引き渡す必要があります。 社内の取り決めや他社とのNDAの都合などからファイルを社外に持ち出せないアドオンの場合、署名を施せないため、やはりFirefox 44以降のバージョンでは利用できないという事態が発生してしまいます。
現在リリースされているFirefox 43までのバージョンや、開発者向けの検証用という位置付けの「Aurora」および「Nightly」では、about:config
などを使って隠し設定のxpinstall.signatures.required
の値をfalse
に変更することで、署名要求を無効化することができます。
Firefox 44以降のリリース版およびベータ版ではこの設定自体が機能しなくなり、署名要求を無効化できなくなることが予告されています。
これについて、まだオフィシャルな場所では情報が公開されていませんが、企業利用向けの長期サポート版であるESR版(Firefox 45ESRおよびそれ以降のバージョン)では、署名要求を無効化する隠し設定を引き続き利用できる状態とする方針である旨、Mozilla Japanから連絡を頂いております。 社外に持ち出せないアドオンを必要とする場合には、この設定を伴ってESR版Firefoxを使うという運用を選択するようにして下さい。
ただしこの措置は、ESR版の運用は技術的な知識のあるシステム管理者がアドオンの安全性を確認した上で行う事が想定されるためということになります。 一般のユーザがESR版でこの設定を使用し未署名のアドオンを使用することは推奨されませんので、ご注意下さい。
FirefoxやThunderbirdはアドオンや設定によるカスタマイズが可能ですが、アドオンを多数インストールして様々な設定を変更した状態だと、何か問題が起こった場合にその原因がどこにあるのかを特定しにくくなります。
とりあえずの対応方法としては、「カスタマイズ内容をすべてリセットしてまっさらの状態に戻す」ことによって問題の解消を図るという方法があります。 FirefoxでもThunderbirdでも、「ヘルプ」メニューから「アドオンを無効化して再起動」を選択したり、Firefoxであれば「トラブルシューティング情報」のタブから「Firefoxをリセット」という操作を行ったりすると、アドオンがインストールされていない状態にすることができます。 ただ、問題の原因究明には繋がりませんし、普段使いの環境を失う事になるため、問題の深刻度によっては「そこまでしなくてもよい」と思う場合もあるでしょう。
このような場合には、一時的な空のプロファイルを使って、普段使いの環境を残したままでクリーンな状態のFirefoxやThunderbirdを並行して動かすことができます。 これを使って問題の原因を調査し、本質的な解決を図った上で改めて普段使いの環境にフィードバックすれば、うまくいけば何も失わずに問題を解消できます。
以下はFirefoxの場合の解説ですが、Thunderbirdの場合も同様ですので、適宜読み替えて読んでいただければ幸いです。
Firefoxのユーザー設定は、インストール先とは別の「ユーザープロファイル」という場所にまとめて保存されています。
これは、WindowsであればC:\Users\(ユーザー名)\Application Data\Roaming
配下に保存されています。
「トラブルシューティング情報」の「プロファイルフォルダ」欄で「ディレクトリを開く」ボタンを押すと、実際に今起動しているFirefoxのプロファイルフォルダを開くこともできます。
ユーザープロファイルは通常は上記の位置にある物が使われますが、フォルダのパスをコマンドラインオプションの-profile
の値として与えると、そのフォルダをユーザープロファイルの置き場所としてFirefoxを起動することができます。
例えばF:\data\firefox
の内容をユーザープロファイルとしてFirefoxを起動する場合は、以下のようにします。
C:\> cd "C:\Program Files (x86)\Mozilla Firefox"
C:\> firefox.exe -profile "F:\data\firefox"
OS X版のFirefoxも、ターミナルからオプションを指定して起動すれば同様のことができます。
$ cd /Applications/Firefox.app/Contents/MacOS
$ ./firefox-bin -profile "/path/to/blank/directory"
この方法で空のフォルダを指定して起動すると、普段使いの環境とは全く別に、アドオンがインストールされておらず、履歴や設定も何も保存されていない、クリーンな状態なFirefoxを起動することができます。 この状態であれば、使い込まれた状態の環境では容易に試すことができないアドオンのインストールやアンインストール、複数を組み合わせた状態での動作検証などを容易に行えます。
検証を行う機会が多いのであれば、firefox.exe
へのショートカットを作成し、プロパティを開いて「リンク先」の欄に以下のような形でコマンドラインオプションを追記しておくことで、コマンドプロンプトを開かずにそのプロファイルでFirefoxを起動することもできます。
"C:\Program Files (x86)\Mozilla Firefox\firefox.exe" -profile "F:\data\firefox"
ユーザープロファイルのパス自体に空白文字が含まれる場合、それ自体を一続きの文字列として示すために二重引用符で囲う必要があることに注意して下さい。
なお、Windows版とOS X版のFirefox 38およびThunderbird 38以降のバージョンでは、-profile
で指定するパスの位置に実際にフォルダが存在しない場合は、フォルダが自動的に作成されるようになっています。
Linux版のFirefoxやThunderbirdでは、フォルダが存在しないとエラーになります(1136620 – -profile option does not create a new directory on linux if the directory does not exist)。
すでにFirefoxが起動している状態で上記のコマンドを実行しても、既に起動しているFirefoxの別ウィンドウが開かれるだけだったり、タブが1つ開かれるだけだったりという結果になります。 これは、コマンドラインから起動されたFirefoxが既に起動しているFirefoxを認識して、既に起動しているFirefoxに処理を委譲し(新しいタブを開く、または新しいウィンドウを開く)、新たに起動された方のFirefox自身はすぐに終了してしまうために起こる現象です。
普段使いの環境でインターネット上の情報を検索しながら、クリーンな環境で検証を進める、というような使い方をしたい場合には-no-remote
オプションを追加で指定する必要があります。
C:\> cd "C:\Program Files (x86)\Mozilla Firefox"
C:\> firefox.exe -profile "F:\data\firefox" -no-remote
このオプションが指定されていると、新たに起動されたFirefoxは既に起動しているFirefoxのことを認識せず、両者を並行して起動できるようになります。 このオプションは、firefox.exeへのショートカットのリンク先にも指定できます。
上記の-profile
オプションと-no-remote
オプションの組み合わせを使うと、普段使いのFirefoxとは別のバージョンのFirefoxを併用することも可能となります。
例えば、普段はリリース版のFirefox 43を使っていて、それとは別にFirefox 45のベータ版の動作を検証したくなることがあるでしょう。 このような場合、Firefox 45のベータ版の方を普段のインストール先とは別の場所にインストールして、これらのオプションを指定して起動すれば、普段使いのFirefox 43で調べ物をしながらFirefox 45のベータ版を試すことができます。
C:\> cd "C:\Program Files (x86)\Mozilla Firefox 45Beta"
C:\> firefox.exe -profile "F:\data\firefox-beta" -no-remote
FirefoxやThunderbirdについて、普段使いの環境には影響を与えずに、クリーンな環境や別のバージョンを並行して起動させる方法をご紹介しました。
様々なカスタマイズを行った状態の普段使いの環境で不具合と思われる現象に遭遇して、それをBugzillaやアドオンの作者に報告しても、Firefoxの開発者やアドオンの開発者の環境では問題が再現しないという場合には、原因究明が難しかったり、時には解決を諦めないといけなかったりという事になる場合もあります。
問題の原因をきちんと切り分けて、再現条件を特定するためにも、不具合に遭遇した時はこの記事で紹介している手順でクリーンな環境のFirefoxを起動して、そこから問題が再現する状態を整えるまでの最短の手順を探るようにしましょう。
昨今、大抵のLinuxディストリビューションにおいては、Systemdが標準採用されています。 ディストリビューションによって提供されているパッケージを使うだけなら、(通常はすでに適切に設定済みのため)普段それほどサービスの依存関係を意識することはありません。 しかし、独自に開発したソフトウェアをサービスとして動かしたりするときには、サービスの依存関係を正しく指定しないと意図したように動作しないという問題に遭遇することがあります。
今回はそんなときに便利なサービスの依存関係を調べる方法を紹介します。
まず、意図した順序でサービスが起動しているか調べるには、systemd-analyze
コマンドを使います。
systemd-analyze
にオプションを何も指定しないと、次のように起動にどれだけかかったかを表示します。*1
% systemd-analyze
Startup finished in 14.348s (kernel) + 7.952s (userspace) = 22.300s
これにplot
オプションをつけて実行すると、SVG形式の結果を得られます。
適当なファイルにリダイレクトしてブラウザで表示すると良いでしょう。
% systemd-analyze plot > something.svg
% firefox something.svg
実際に描画してみた結果は次のようになりました。
凡例については次の通りになっています。
このように、どの順序でサービスが起動されたのか、またどれくらい時間がかかっているのかがわかりやすく表示されます。 サービスのunitファイルの記述に漏れがあり、意図しないタイミングで起動されてしまっていたケースなどではこれがヒントになります。
今度は、unitファイルの記述をもとにサービスの依存関係を調べてみましょう。
systemctl
にはlist-dependencies
というオプションがあり、依存関係を調べてツリー表示できます。
ここでいう依存関係はRequires=
やWants=
といった、必要となるunitに着目した依存関係です。
起動順に着目して調べるには、list-dependencies
に--before
や--after
を併せて指定します。
--before
を指定するとunitファイルのBefore=
ディレクティブをたどって依存関係を表示します。
同様に--after
を指定するとunitファイルのAfter=
ディレクティブをたどって依存関係を表示します。
例えば、getty@tty1
がどのサービスの前に起動していないといけないことになっているかを調べるには次のようにします。
% systemctl list-dependencies --before getty@tty1
getty@tty1.service
● ├─getty.target
● │ └─multi-user.target
● │ ├─systemd-update-utmp-runlevel.service
● │ └─graphical.target
● │ └─systemd-update-utmp-runlevel.service
● └─shutdown.target
これは、実際にgetty@tty1.service
に以下の記述があることと一致しています。
# If additional gettys are spawned during boot then we should make
# sure that this is synchronized before getty.target, even though
# getty.target didn't actually pull it in.
Before=getty.target
IgnoreOnIsolate=yes
Systemdのサービスの依存関係を調べる方法として、systemd-analyze
とsystemctl
の使い方を紹介しました。
Systemdで依存関係にまつわるトラブルに遭遇したら、使ってみると便利かも知れません。
*1 デフォルトでtimeオプションを指定したのと同じ挙動になるため。