Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

もしもラムダの中で例外が発生したら(前編)

ある日、 id:cero-tJJUGの重鎮たちと話している中で、とある宿題をもらいましたとさ。

「Java8のラムダの中で例外が発生したら、どうなるんだろう?」


こんにちは、アキバです。
もう皆さんはJava8を使ってみましたか?
f:id:acro-engineer:20140311021635j:plain

とりあえずインストールしてみた人!

・・はーい (おまえか


という冗談はさておき、
今回は、id:cero-t に代わって私が冒頭のお題を調べてみました。

1. SerialStreamで動かしたラムダで例外が発生したら

まずは、小手調べにシングルスレッドの場合を見てみましょう。


検査例外が発生するようなコードをラムダに書いてみると、コンパイルエラーになります。
こんなコードです。

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(W_FILENAME))) {
    // writer.write() がIOExceptionをスローするので、catchしろと言われる
    lines.forEach(s -> writer.write(s + '\n'));
} catch (IOException ioex) {
    System.out.println("IOException in Writer-try.");
    ioex.printStackTrace(System.out);
    throw new UncheckedIOException(ioex);
}


そこで、ラムダの中で例外をハンドリングしてみると、確かにエラーが出なくなります。
こんなコードです。

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(W_FILENAME))) {
    lines.forEach(s -> {
        // ラムダの中で例外をcatchする
        try {
            writer.write(s + '\n');
        } catch (IOException ex) {
            System.out.println("IOException in lambda.");
            ex.printStackTrace(System.out);
            throw new UncheckedIOException(ex);
        }
    });
} catch (IOException | RuntimeException ex) {
    System.out.println("Exception in Writer-try.");
    ex.printStackTrace(System.out);
    throw ex;
}


動かしてみるとどうなるんでしょうね。

あ、今回は、writer.write()で例外を発生させるのも面倒なので、

writer.write(s + '\n');

throw new IOException("IOException in writer");

に変えて動かしています。


動かした結果、こうなりました。

IOException in lambda.
java.io.IOException: IOException in writer
	at study.java8.lambda.StreamSample.lambda$streamExceptionSample1$0(StreamSample.java:68)
	at study.java8.lambda.StreamSample$$Lambda$1/149928006.accept(Unknown Source)
	at java.util.ArrayList.forEach(ArrayList.java:1234)
	at study.java8.lambda.StreamSample.streamExceptionSample1(StreamSample.java:65)
	at study.java8.lambda.StreamSample.main(StreamSample.java:33)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Exception in Writer-try.
java.io.UncheckedIOException: java.io.IOException: IOException in writer
	at study.java8.lambda.StreamSample.lambda$streamExceptionSample1$0(StreamSample.java:72)
	at study.java8.lambda.StreamSample$$Lambda$1/149928006.accept(Unknown Source)
           :
          (略)
Caused by: java.io.IOException: IOException in writer
	at study.java8.lambda.StreamSample.lambda$streamExceptionSample1$0(StreamSample.java:68)
	... 9 more

最初の行に出た例外のメッセージが「IOException in lambda.」となっていることから、ラムダ呼び出しの中で例外をcatchしていることがわかります。

次に、「Exception in Writer-try.」と出ています。

おぉ、ラムダ呼出しの外側で、ラムダがスローした例外をcatch出来ていますね。
UncheckedIOExceptionの中身は、ラムダ処理自体で発生した例外でした。


つまり、ラムダの中で検査例外が発生するようなコードは、
無名内部クラスと同じように例外をcatchして処理する必要があるということがわかりました。
ただし、無名内部クラスと大きく違うのは、メソッド定義ではないので、例外の宣言は出来ません。
よって、スロー出来るのは検査例外以外(RuntimeExceptionのサブクラスなど)でなくてはいけないということになりますね。


ふむふむ。


では、いよいよ本題です。

2. ParallelStreamで動かしたラムダで例外が発生したら

Parallelってことは、マルチスレッド動作なわけじゃないですか。

  • 発生した例外は誰が受け取るのか?どこまで伝播するのか?
  • 例外が発生したスレッド全てでcatch処理が行われるのか?
  • 例外が発生しなかった他のスレッドはどのような影響を受けるのか?
  • そもそもちゃんと全部のスレッド処理が終了するのか?

皆さんも気になりますよね?
私も気になります。
(だから、それが本題なんだってば)


まずは、こんなコードを書いてみます。

try {
    List<String> strArray = Arrays.asList("abc", "def", "xxx", "ghi", "jkl", "xxx", "pqr", "stu");
    strArray.parallelStream().forEach(s -> {
        System.out.println("ラムダ開始: id=" + Thread.currentThread().getId());
        try {
            Thread.sleep(100L);
            if (s.equals("xxx")) throw new RuntimeException("ラムダ内で例外: id=" + Thread.currentThread().getId());
        } catch (RuntimeException ex) {
            System.out.println("ラムダ内で例外発生: id=" + Thread.currentThread().getId());
            throw ex;
        } catch (InterruptedException e) {
            e.printStackTrace(System.out);
        }
        System.out.println("ラムダ終了: id=" + Thread.currentThread().getId());
    });
} catch (Exception th) {
    System.out.println("外側で例外をcatch");
    th.printStackTrace(System.out);
}

要するに、文字列が "xxx" だったら例外を吐く、それ以外は正常終了するラムダ処理です。
これをParallelStreamで実行するものです。

"xxx" は2つあるので、2回例外が発生するようになっていますよね。

さて、これを動かしてみるとこうなります。

ラムダ開始: id=1
ラムダ開始: id=15
ラムダ開始: id=14
ラムダ開始: id=16
ラムダ開始: id=12
ラムダ開始: id=17
ラムダ開始: id=13
ラムダ開始: id=18
ラムダ終了: id=18
ラムダ終了: id=13
ラムダ終了: id=17
ラムダ終了: id=14
ラムダ内で例外発生: id=12
ラムダ内で例外発生: id=1
ラムダ終了: id=16
ラムダ終了: id=15
外側で例外をcatch
java.lang.RuntimeException: ラムダ内で例外: id=12

ふむふむ、例外もラムダ呼出しの外側でcatchできました

…と思ったら、ラムダ呼出しの外側でcatchできたのは、id=12のスレッドのみでした。


id=1のスレッドで発生した例外はどこに行ったのでしょうか?


では、もう一回実行してみましょう。

ラムダ開始: id=1
ラムダ開始: id=14
ラムダ開始: id=15
ラムダ開始: id=13
ラムダ開始: id=16
ラムダ開始: id=17
ラムダ開始: id=12
ラムダ開始: id=18
ラムダ終了: id=16
ラムダ終了: id=15
ラムダ内で例外発生: id=13
ラムダ内で例外発生: id=1
ラムダ終了: id=14
ラムダ終了: id=17
ラムダ終了: id=12
外側で例外をcatch
java.lang.RuntimeException: ラムダ内で例外: id=13

ふむふむ...あれ?
今度は、id=18のスレッドで「ラムダ終了」が出ませんでしたよ!


頻度は高くないようですが、ParallelStreamの並列処理には、実はこんな不安定なところがあるようです。


まとめると、

  • なぜ、最初の例外しかラムダの外側でcatchできないのか?
  • なぜ、終了が出なかったスレッドが存在するのか?
  • 終了が出なかったスレッドは、実際には終了したのか、それとも残っているのか?
  • ParallelStreamって、Fork-Joinで待ってないの?

そんなところが疑問ですよね。



後編ではその謎に迫ります!

では。

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

あなたのJavaコードをスッキリさせる、地味に便利な新API 10選(後編)

こんにちは。
アキバです。

本日3/18、ついに、Java8が正式リリースされますね!
もうダウンロードされましたか?ってまだですかね?私はまだです(だって公開前にエントリ書いてるんだもんね)

2014/03/19追記:Oracleのページが更新されました!→こちら


さて、前回に続いて、Java8で追加された地味で便利なAPIを紹介していきます。

今回は、みんな大好きMapConcurrent、あとちょびっとComparatorです。

f:id:acro-engineer:20140311021635j:plain

3. Map操作編

(1) Map#getOrDefault()

これまでは、Mapから値を取得してnullだったらデフォルト値を使用する、みたいなコードを以下のように書いていたと思います。

Map<String, String> map;    // 何らかのMap

String value = map.get("key");
if (value == null) {
    value = defaultValue;
}

Map#getOrDefault()を使うと、この処理が1行になります。

String value = map.getOrDefault("key", defaultValue);

それだけです。ちょっとしたところからスッキリしますね。

(2) Map#replace()

Map#replace()を使うと、該当するキーに対する値を入れ替えられます。

キーが存在することが必須条件ですが、値の指定には2種類あります。

1つ目は、キーさえあれば、強制的に値を書き換えるもの。

Map<String, String> map;    // 何らかのMap
map.replace("key1", "newValue");

この場合は、key1が存在すれば、値はかならずnewValueになります。
key1が存在しないと、何も起こりません。

2つ目は、キーがあって値が一致したものだけ書き換えるもの。

Map<String, String> map;    // 何らかのMap
map.replace("key1", "oldValue", "newValue");

この場合、key1がoldValueである場合に限り、newValueになります。
それ以外の場合は、何も起こりません。

いずれの呼び出しも、戻り値として置換前の値が得られます。
置換しなかった場合は、nullとなるので、書き換わったかどうかを調べることもできます。

(3) Map#computeIfPresent()

Map#computeIfPresent()は、キーが存在している(かつnullではない)時に、値を加工するためのメソッドです。

...なんとなく便利そうですが、いまいちイメージしづらいですね。
長々と説明するよりも、実際のコードを見てみましょう。

例として、Mapのキーを探索して、存在する場合に先頭に「★」を追加する処理を書いてみます。


今までは、以下のように書いていたと思います。

Map<String, String> map;   // 何らかのMap

// キーが存在する名前だけ、先頭に「★」を追加する
String[] keys = new String[] { "a", "c", "d" };
for (String key : keys) {
    if (map.get(key) != null) {
        String oldValue = map.get(key);
        String newValue = '★' + oldValue;
        map.put(key, newValue);
    }
}


Java8では、以下のように書けます。

Map<String, String> map;   // 何らかのMap

// キーが存在する名前だけ、先頭に「★」を追加する
String[] keys = new String[] { "a", "c", "d" };
for (String key : keys) {
    map.computeIfPresent(key, (k, s) -> '★' + s);
}

イディオム的にコードを書くよりも、computeIfPresentという名前で何をしたいのか/何をしているのかがハッキリして良いと思います。
しかも、コードはスッキリ。

ラムダ式になっているところは、BiFunctionというJava8で追加された関数インタフェースです。
ちなみに、Map#computeIfPresent() は default メソッドになっていて、以下のコードであるとAPIドキュメントに書かれています。

if (map.get(key) != null) {
    V oldValue = map.get(key);
    V newValue = remappingFunction.apply(key, oldValue);
    if (newValue != null)
        map.put(key, newValue);
    else
        map.remove(key);
}

最初に書いたコードとほとんど同じですね。
また、これを見ると、BiFunctionに渡される2つの引数は、1つ目がキー、2つ目が(処理前の)値であることがわかります。

4. Concurrent編

(1) LongAdder

Javadocを見ると、「初期値をゼロとした、1つ以上の値の合計を扱う」とあります。
要は、値を合計するためのクラスなのですが、java.util.concurrent.atomicパッケージに属しているだけあって、マルチスレッドからのアクセスに対応しています。

シングルスレッドで使っているとつまらないのですが、こんな感じです。

long[] longArray = { 1, 2, 3, 4, 5 };

LongAdder adder = new LongAdder();
LongAdder count = new LongAdder();
for (long longValue : longArray) {
    adder.add(longValue);
    count.increment();
}

System.out.println("elements count=" + count.sum() + ", sum=" + adder.sum());
// → elements count=5, sum=15 と表示される

他にも、sumThenReset() があるので、一定期間毎の合計を出したりできるかもしれません。

似たような用途に AtomicLong も使えるのですが、APIドキュメントを見ると
「スレッドの競合が高い状況下では LongAdderの方がメモリを消費する代わりに、高速に動作する」
という主旨のコメントが書かれています。

Java8のソースを見る限りでは、合計値の算出タイミングの違いが影響しているようですね。

AtomicLongは、値を追加(addAndGet)する毎に計算を行っていますが、LongAdderの方はCellという内部用のオブジェクトを配列で保持するようになっていて、合計を参照する(sumなど)のタイミングで初めて合計値を計算する仕組みになっているようです。

ということは、複数スレッドが競合するタイミングで計算を含んだ処理でロックの取り合いにはならないということでしょうか。


どのような条件で、どれだけ高速なのでしょうか?
今回もベンチマークしようと思っていたのですが、海外のエントリでベンチマークをとった結果が出ていました。
→日本語訳の引用あり:Java 8ニュース:新しいアトミックナンバーを含むRC版を公開、モジュール化は外れる
 (「新しいアトミックナンバーの実装」という項を見てください)
→原文:Java 8 Performance Improvements: LongAdder vs AtomicLong - Palomino Labs Blog

これによると、シングルスレッドではAtomicLongの方が速いけど、マルチスレッドで競合アクセスさせた場合はLongAdderの方が性能が良いとなっています。純粋なカウントアップ処理を行っているとのことですが、特性としてはドキュメントに書かれている通りになりました。

(2) LongAccumulator

Javadocを見ると、「1つ以上の値の集合を提供された関数で更新する」とあります。
なんとなくLongAdderと同じようなクラスですが、コンストラクタにはLongBinaryOperatorを
指定することになっており、ちょっと凝った動作ができるようです。

簡単な例として、値の2乗を合計するようにしてみましょう。

long[] longArray = { 1, 2, 3, 4, 5 };

LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y * y, 0L);
for (long longValue : longArray) {
    accumulator.accumulate(longValue);
}

System.out.println("square sum=" + accumulator.get());
// → square sum=55 と表示される
//    (1 + 4 + 9 + 16 + 25 = 55)

こちらも、マルチスレッドで処理する場合には利用を検討してみましょう。

5. Comparator編

(1) naturalOrder() / reverseOrder()

最後に、皆さんもよく使っているであろう Comparator で見つけたメソッドです。

唐突ですが、今までJava8の勉強をしてきて、たくさんラムダ式を見たりしてきましたよね。
なので、「文字列を自然順序付けの昇順/降順のソートをする」と聞くと、つい、以下のように書きたくなるかもしれません。

// 昇順にソートする
Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));

// 降順にソートする
Arrays.sort(array, (s1, s2) -> s2.compareTo(s1));

このくらいの処理ならば、ラムダ式でも十分にわかりやすいとは思いますが、Comparator#naturalOrder() と Comparator#reverseOrder() を使うとよりシンプルに記述できます。

// 昇順にソートする
Arrays.sort(array, Comparator.naturalOrder());

// 降順にソートする
Arrays.sort(array, Comparator.reverseOrder());

メソッドの名前からソート順を理解しやすく、さらにコードもスッキリしましたね。


f:id:acro-engineer:20140311021635j:plain:small

いかがでしたでしょうか。

他にも追加されたAPIはまだまだたくさんあります。

前回書いたベンチマークのように、構文は便利になったが、果たして実用に耐えられるのか?ということは常に注意して使わなければなりませんし、その為に中身の動作をよく理解しておく必要があります。

それでも、知らなければ使おうとも思わないという面もあると思います。

ぜひ、皆さん自身でも試してみて、開発効率を上げていきましょう!


ではでは~

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

あなたのJavaコードをスッキリさせる、地味に便利な新API 10選(前編)

こんにちは!
アキバです。

...T3ブログは初登場かもしれません。ハジメマシテ。
以後お見知りおきを。

いよいよ、2014年3月、Java8が正式公開されますね。
f:id:acro-engineer:20140311021635j:plain

なんと言っても、Java8の注目機能はラムダ式ですので、ラムダ式型推論に関する記事は多いです。
世の中で「Java8」と検索すると、皆さんいろいろと記事を書かれているので、おおよその事はこれで分かっちゃうような気がします。

が、

実は地味に便利なAPIが追加されていたりすることを最近知りました。

これはあまり触れられていないぞ、と。

というわけで、このエントリでは、あまり日本語で情報の無い、しかし地味に便利なAPIに実際に触れてみます。

大事なところなので2回強調して書いてみました。


今回は、前編として4つ紹介します。

APIの紹介と言いつつ、コードにはラムダ式を使ったコードが普通に出てるので、ご了承ください。
 (むしろ、こういったコードを見ながらラムダ式の実際を理解するのもイイかも?)

1. 文字列結合編

これまでのコード

これまでの文字列結合は、おおよそ以下のような感じだと思います。
ここでは、Stringの配列からカンマ区切りの文字列を作る例を考えてみましょう。
(この例は配列ですが、通常はList<String>を使うことが多いと思います)

String[] strArray = { "abc", "def", "123", "456", "xyz" };
String separator = ",";

StringBuilder sb = new StringBuilder();
for (String str : strArrayt) {
    if (sb.length() > 0) {
        sb.append(separator);
    }
    sb.append(str);
}

System.out.println(sb.toString());
// → abc,def,123,456,xyz という文字列が表示される

こんな処理を簡単に書くためのAPIがJava8で追加されました。
(もっと昔からあっても良かったようなものですよね…)

主なものは次の2つです。

(1) String#join(CharSequence, Iterable)

1つ目の新APIは、Stringクラスに追加されたjoinメソッド
文字列を直接並べることもできますが、メインとなるのは配列やListなどのIterableを使う方でしょう。
String#join()を使うと、先ほどのコードは以下のように書き換えることが出来ます。

String[] strArray = { "abc", "def", "123", "456", "xyz" };
String separator = ",";

System.out.println(String.join(separator, strArray));
// → abc,def,123,456,xyz という文字列が表示される

なんと、ループもStringBuilderもなく一行で書けてしまいました。スッキリ。

(2) StringJoiner

2つ目の新APIは、StringJoinerというクラスです。
目的はString#join()とほぼ同じです。
というか、このクラスは、String#joinの中からも呼び出されていたりします

早速、同じように書き換えてみましょう。

String[] strArray = { "abc", "def", "123", "456", "xyz" };
String separator = ",";

StringJoiner sj = new StringJoiner(separator);
for (String str : strArray) {
    sj.add(str);
}

System.out.println(sj.toString());
// → abc,def,123,456,xyz という文字列が表示される

…ループもあるし、意外とスッキリしてませんね(笑)

使い方もStringBuilderと似ていますが、主な違いは以下の3点くらいですね。

  1. 区切り文字を自分で追加しなくなった
  2. 接頭辞と接尾辞を指定できる(CSV全体をカッコで囲むみたいなことができます)
  3. 要素を追加していない時の文字列を指定できる

れっつ、ベンチマーク

ところで、いつのバージョンでも「文字列結合の性能ネタ」はありますが、上記の新APIは、パフォーマンス的にはどうなんでしょうか?

ここでは、以前に @cero-t も紹介していた マイクロベンチマークツール「JMH」を使って、10000個の5バイト文字列("abcde")をカンマ区切りで連結する処理の実行時間を測定してみました。
(※正式リリース前のJava8で実施しているため、参考情報としてご覧ください)

パターンは以下の5つ。
(カッコ内は、マイクロベンチマーク用に記述したメソッド名になります)

  1. StringBufferを使って結合する (bufferJoin)
  2. StringBuilderを使って結合する (builderJoin)
  3. String#joinを使って結合する (joinJoin)
  4. StringJoinerを使って結合する (joinerJoin)
  5. 「+」で文字列を結合する(※いわゆるアンチパターン)(plusJoin)

※2014/03/18 ソースコードは、GitHubからどうぞ。
https://github.com/otomac/StringJoinBenchmark



実行条件:

java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b127)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b69, mixed mode)

JMH実行時のコマンドラインは「-wi 5 -i 10 -f 10」です。
つまり、以下のようになります。
  (A) ウォームアップ実行 5回
  (B) 計測のための実行 10回
  (C) 上記(A)+(B)の繰り返し 10回

計測のための実行は、各パターン100回ずつということになります。

さて、結果は以下のようになりました。

Result : 0.005 ±(99.9%) 0.001 ops/ms
  Statistics: (min, avg, max) = (0.003, 0.005, 0.005), stdev = 0.001
  Confidence interval (99.9%): [0.004, 0.006]


Benchmark                             Mode   Samples         Mean   Mean error    Units
s.j.b.StringJoinStudy.bufferJoin     thrpt       100        5.134        0.033   ops/ms
s.j.b.StringJoinStudy.builderJoin    thrpt       100        5.386        0.023   ops/ms
s.j.b.StringJoinStudy.joinJoin       thrpt       100        5.350        0.019   ops/ms
s.j.b.StringJoinStudy.joinerJoin     thrpt       100        5.357        0.044   ops/ms
s.j.b.StringJoinStudy.plusJoin       thrpt       100        0.005        0.000   ops/ms

この結果から、String#joinを使うパターン(joinJoin)とStringJoinerを使うパターン(joinerJoin)は、StringBuilderを使うパターン(builderJoin)とほぼ同等の速度が出せることがわかります。
StringBufferを使うパターン(bufferJoin)は、想定通り若干遅いくらいの結果になりました。
「+」を使うパターン(plusJoin)も、予想通り論外の性能でしたね(笑

このように、配列やリストのように、既に連結する文字列の要素が揃っている場合はString#joinやStringJoinerを使っても性能に遜色はないことがわかりました。
ぜひ使っていきたいところですね。

2. ファイル読み込み編

これまでのコード

これまでのテキストファイル読み込みは、おおよそ以下のような感じだと思います。
ただ読み込むだけだとつまらないので、XMLファイルの"<" と ">"を丸括弧 "(" と ")" に置換するサンプルを考えてみます。

※2014/03/18追記:うらがみ (id:backpaper0) さんからご指摘いただいて、Pathオブジェクトの生成コードを修正しました。コメントありがとうございます。

List<String> lines = new ArrayList<String>();

Path path = Paths.get("data.xml");
String line;
try (BufferedReader br = Files.newBufferedReader(path)) {
    while ((line = br.readLine()) != null) {
        String replaced = line.replaceAll("<", "(").replaceAll(">", ")");
        lines.add(replaced);
    }
} catch(IOException ex) {
    // 省略
}

whileの条件文に行読み込みの呼び出しがネストしていたりして、若干ややこしい感じがします。
かといって以下のように書くのも間延びする感じがしますね。

List<String> lines = new ArrayList<String>();

Path path = Paths.get("data.xml");
try (BufferedReader br = Files.newBufferedReader(path)) {
    while (true) {
        String line = br.readLine();
        if (line == null) {
            break;
        }
        String replaced = line.replaceAll("<", "(").replaceAll(">", ")");
        lines.add(replaced);
    }
} catch(IOException ex) {
    // 省略
}

さて、Java8ではそんなテキストファイル読み込みも便利にしてくれるAPIが追加されています。
ここでも、2種類見てみましょう。

(1) BufferedReader#lines()

InputStreamやReaderがあらかじめある場合は、こちらの方を使うことになるでしょう。
先ほどのコードは以下のようになります。
(書き換えた結果、結局Pathを作ってBufferedReaderを作っていますが、そのくらいなら良いのかなと)

List<String> lines = new ArrayList<String>();

Path path = Paths.get("data.xml");
try (BufferedReader br = Files.newBufferedReader(path, Charset.forName("UTF-8"))) {
    br.lines()
       .forEach(l -> { strList.add(l.replaceAll("<", "(").replaceAll(">", ")")); });
} catch(IOException ex) {
    // 省略
}

先ほどのコードに比べて、whileループが無いのでスッキリ。

ちなみに、上の例に書いたように、Filesクラスの多くのメソッドでは、Charsetで文字コードを指定することができます。
これも、今まではFileReaderでは文字コードを指定できず、InputStreamReaderを介して指定しなければならなかったのですが、Filesクラスで指定できるようになって、非常に便利になったところですね。
(※追記: UTF-8のような標準的な文字コードは、StandardCharsets.UTF_8でも可です)


読み込んだデータの加工をすることを考えると、ラムダ式やStream APIに慣れておかないといけませんが、単にリストに格納するだけなら以下のようにするのが簡単です(※これはJava7からある書き方です)。

※2014/03/13追記: id:nowokay さんから指摘をいただき、ファイル読み込みを Files#readAllLines() に変更しました。コメントありがとうございます。

Path path = Paths.get("data.xml");
List<String> allLines = Files.readAllLines(path, Charset.forName("UTF-8"));

ただし、メモリを大量に消費する危険性がありますので、大きいファイルを読み込む処理で使ってはいけません。

(2) Files#lines()

ファイルのパスが分かっているだけの場合や、Files#find()を使う場合などは、こちらの方を使うことになるでしょう。

先ほどのコードは以下のようになります。

 List<String> lines = new ArrayList<String>();
 
  Path path = Paths.get("data.xml");
 try (Stream<String> stream = Files.lines(path, Charset.forName("UTF-8"))) {
     stream.forEach(l -> { strList.add(l.replaceAll("<", "(").replaceAll(">", ")")); });
 } catch(IOException ex) {
     // 省略
 }

わざわざファイルからInputStreamやReaderを作らなくてよいので、非常にスッキリしてますね。

…なんとなく予想付いちゃってる方もいらっしゃるかと思いますが、こちらも、内部でFiles.newBufferedReader()を使っているので、ほとんど同じです。



f:id:acro-engineer:20140311021635j:plain:small

ここまで、前半として、文字列操作とファイルからのテキスト読み込みについての追加APIを紹介しました。
なんで、今までこんなメソッドが無かったのか?というくらい普通に使いたくなるものじゃないでしょうか?

ある意味、経験を積んだJavaコーダーさんなら、イディオムとして手が勝手に動くようなコードもあるかと思います。
でも、これからは自分の手じゃなくてコンパイラに仕事をさせて、短くなったコードでもうちょっと違うアイデアを考えていきたいものですね。

次回(後編)は、Map操作とConcurrent系のちょっとしたAPIの追加について、です。

では。


Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Apache 初回リリースのStorm0.9.1のリリース内容は?

こんにちは。kimukimuです。

徐々に暖かくなってきていますね。春は目前です。

さて、つい先日Apache Stormとして初リリースとなる「0.9.1-incubating」がリリースされました!
#ちなみに前回リリースは「0.9.0.1」です。

今回リリース内容は大きく分けて、Apache Incubatorに移行したため発生したものと、
機能追加/不具合対応によるものの2つがあります。

節目のリリースですので両方についてリリースノートより主要な項目を紹介しますね。

1. Apache Incubatorとしてのリリース内容

ライセンスの変更

ライセンスが「Eclipse Public License - v 1.0」から「Apache License Version 2.0」に変更になってます。
今回の変更で今まで使っていた方が使えなくなる・・・ということはないのですが、
きちんとライセンスは確認した上で使うようにしましょう。

ビルドツールの変更

ビルドツールがLeiningen > Mavenに変更になりました。
これも使う上で何かが変わるというわけではありませんが、
Mavenになったことによって、JavaをやっているエンジニアがStormのソースを修正してビルドを行う・・・
ということがやりやすくなったとは思います。
ただ、Stormのコア部分がClojureで記述されていることには変わりありませんので、あしからず。

Mavenリポジトリ上のGroupIdの修正

今回のリリースによって、GroupIdが「storm」 > 「org.apache.storm」に変更になっています。
バージョンアップを行う時には気をつけましょう。

2. 機能追加/不具合対応によるリリース内容

通信モジュールのデフォルトがZeroMQからNettyに変更

今回のバージョンから通信モジュールのデフォルトがZeroMQからNettyに変更されました。
Making Storm fly with Netty | Yahoo Engineeringを見るに、パフォーマンス自体もNettyの方が高いようです。
加えて、依存性も少なくなるため、デフォルトになったのは納得がいく結果ではありますね。

Storm-UIの各項目にツールチップで解説を表示

Storm-UIで表示される各種項目に対してマウスオーバーした際にツールチップで解説が表示されるようになりました。
Storm-UIの動きはこれまでは実際にしばらく使ってみないといまいちわからないことも多かったため、
これは有難い機能追加ですね。

NimbusにTopologyをSubmitする際、設定に対するバリデーションが追記

NimbusにTopologyをSubmitする際に設定値に対する定型的なバリデーションが行われるようになりました。
TopologyにSubmitする際にはじかれない場合、クラスタ上でTopologyが走り出してからWorkerプロセスが
エラーで落ちて、復活してを繰り返す・・・ということが発生するため、意図しない動作を防止してくれますね。

「storm jar」コマンドによるTopology起動がWindows上で動作しない問題に対応

Stormは0.9.0の時点でZeroMQが必須では無くなったことによってWindows上でも動作するようになっていました。
ですが、一部のコマンドが動作しない個所があったため、問題が対応され、Windows上でもStormのフル機能が使用できるようになりました。
主流では無いとは思いますが、Windows上でもそれなりに使われているようですね。

3.今回のリリースのまとめ

今回のリリースはApacheに移行したことによるメンテナンスと、後は使い勝手に関わる箇所のリリースで、
何か大きな新機能が追加されたということはありませんでした。

ですが、今回のリリースでApacheへの移行が正式に完了したということにはなります。
以後はApacheとしてのリリースが続いていくはずですので、この先に期待、ですね。
それでは。
f:id:acro-engineer:20140305072059j:plain

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Elasticsearchソースコードリーディング~内部構造把握の第一歩~

こんにちは! ツカノ(@)です。

ElasticsearchKibanaを使うとログなどを簡単に可視化できるため、利用者が急速に増えてきています(私もそのひとりです)。様々なブログで紹介されているKibana画面はとても素敵で、積極的に使いたくなります。
ただ、それなりの規模でElasticsearchを利用するには、中身を知ったり、データ設計や運用面なども考える必要がありますが、そのあたりの情報はまだ十分ではないように思います。そこで、Elasticsearchの中身をもっと知るために、ひとりでひっそりソースコードリーディングをしてみました。
f:id:acro-engineer:20140131080117p:plain

今回利用したElasticsearchのバージョンは1.0.0.RC2です。以下の場所から実行媒体とソースコードをダウンロードして確認しています。また、確認はWindowsで行っています。

1.ElasticsearchはどんなJVM引数で起動するのか?

まずは、実行媒体(zip)を展開します。ひとまずデフォルト設定のままで、elasticsearch.batを実行して起動します。

以下のようなログがコンソールに表示されて、起動完了です。

[2014-02-07 06:35:10,225][INFO ][node                     ] [Blind Faith] version[1.0.0.RC2], pid[9052], build[a9d736e/2014-02-03T15:02:11Z]
[2014-02-07 06:35:10,225][INFO ][node                     ] [Blind Faith] initializing ...
[2014-02-07 06:35:10,229][INFO ][plugins                  ] [Blind Faith] loaded [], sites []
[2014-02-07 06:35:13,525][INFO ][node                     ] [Blind Faith] initialized
[2014-02-07 06:35:13,526][INFO ][node                     ] [Blind Faith] starting ...
[2014-02-07 06:35:13,743][INFO ][transport                ] [Blind Faith] bound_address {inet[/0:0:0:0:0:0:0:0:9300]}, publish_address {inet[/172.20.10.4:9300]}
[2014-02-07 06:35:16,971][INFO ][cluster.service          ] [Blind Faith] new_master [Blind Faith][NYDlh_0TRJeafDu2KZxB1A][Poincare][inet[/172.20.10.4:9300]], reason: zen-disco-join (elected_as_master)
[2014-02-07 06:35:17,007][INFO ][discovery                ] [Blind Faith] elasticsearch/NYDlh_0TRJeafDu2KZxB1A
[2014-02-07 06:35:17,039][INFO ][gateway                  ] [Blind Faith] recovered [0] indices into cluster_state
[2014-02-07 06:35:17,140][INFO ][http                     ] [Blind Faith] bound_address {inet[/0:0:0:0:0:0:0:0:9200]}, publish_address {inet[/172.20.10.4:9200]}
[2014-02-07 06:35:17,141][INFO ][node                     ] [Blind Faith] started


さて、VisualVMを起動して、Elasticsearchに接続してみましょう。以下の図の赤枠で囲った部分にElasticsearchが見えていますね。
f:id:acro-engineer:20140207084727p:plain


「概要」タブをクリックして、JVM引数を確認してみると、以下のようになっています。
(es.path.homeは実行媒体を展開したディレクトリによって異なります)

-Xms256m
-Xmx1g
-Xss256k
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+HeapDumpOnOutOfMemoryError
-Delasticsearch
-Des-foreground=yes
-Des.path.home=C:\elasticsearch-1.0.0.RC2

これら設定値はelasticsearch.batの中に記載されているため、JVM引数を変更する場合はこのファイルを修正してください。Linux版の場合はelasticsearchelasticsearch.in.shにまたがってJVM引数の設定が記載されています。

2.Elasticsearchのスレッド構成は?

引き続き、VisualVMを操作します。「スレッド」タブから「スレッドダンプ」ボタンを押して、スレッドダンプを取得してみましょう。ちょっと長いですが、取得したスレッド一覧は以下の通りになりました。(見やすいように並び替えています)

Attach Listener
C2 CompilerThread0
C2 CompilerThread1
Concurrent Mark-Sweep GC Thread
DestroyJavaVM
elasticsearch[Blind Faith][[timer]]
elasticsearch[Blind Faith][[ttl_expire]]
elasticsearch[Blind Faith][clusterService#updateTask][T#1]
elasticsearch[Blind Faith][discovery#multicast#receiver][T#1]
elasticsearch[Blind Faith][generic][T#1]
elasticsearch[Blind Faith][http_server_boss][T#1]{New I/O server boss #51}
elasticsearch[Blind Faith][http_server_worker][T#1]{New I/O worker #35}
elasticsearch[Blind Faith][http_server_worker][T#2]{New I/O worker #36}
elasticsearch[Blind Faith][http_server_worker][T#3]{New I/O worker #37}
elasticsearch[Blind Faith][http_server_worker][T#4]{New I/O worker #38}
elasticsearch[Blind Faith][http_server_worker][T#5]{New I/O worker #39}
elasticsearch[Blind Faith][http_server_worker][T#6]{New I/O worker #40}
elasticsearch[Blind Faith][http_server_worker][T#7]{New I/O worker #41}
elasticsearch[Blind Faith][http_server_worker][T#8]{New I/O worker #42}
elasticsearch[Blind Faith][http_server_worker][T#9]{New I/O worker #43}
elasticsearch[Blind Faith][http_server_worker][T#10]{New I/O worker #44}
elasticsearch[Blind Faith][http_server_worker][T#11]{New I/O worker #45}
elasticsearch[Blind Faith][http_server_worker][T#12]{New I/O worker #46}
elasticsearch[Blind Faith][http_server_worker][T#13]{New I/O worker #47}
elasticsearch[Blind Faith][http_server_worker][T#14]{New I/O worker #48}
elasticsearch[Blind Faith][http_server_worker][T#15]{New I/O worker #49}
elasticsearch[Blind Faith][http_server_worker][T#16]{New I/O worker #50}
elasticsearch[Blind Faith][management][T#1]
elasticsearch[Blind Faith][management][T#2]
elasticsearch[Blind Faith][riverClusterService#updateTask][T#1]
elasticsearch[Blind Faith][scheduler][T#1]
elasticsearch[Blind Faith][transport_client_boss][T#1]{New I/O boss #17}
elasticsearch[Blind Faith][transport_client_timer][T#1]{Hashed wheel timer #1}
elasticsearch[Blind Faith][transport_client_worker][T#1]{New I/O worker #1}
elasticsearch[Blind Faith][transport_client_worker][T#2]{New I/O worker #2}
elasticsearch[Blind Faith][transport_client_worker][T#3]{New I/O worker #3}
elasticsearch[Blind Faith][transport_client_worker][T#4]{New I/O worker #4}
elasticsearch[Blind Faith][transport_client_worker][T#5]{New I/O worker #5}
elasticsearch[Blind Faith][transport_client_worker][T#6]{New I/O worker #6}
elasticsearch[Blind Faith][transport_client_worker][T#7]{New I/O worker #7}
elasticsearch[Blind Faith][transport_client_worker][T#8]{New I/O worker #8}
elasticsearch[Blind Faith][transport_client_worker][T#9]{New I/O worker #9}
elasticsearch[Blind Faith][transport_client_worker][T#10]{New I/O worker #10}
elasticsearch[Blind Faith][transport_client_worker][T#11]{New I/O worker #11}
elasticsearch[Blind Faith][transport_client_worker][T#12]{New I/O worker #12}
elasticsearch[Blind Faith][transport_client_worker][T#13]{New I/O worker #13}
elasticsearch[Blind Faith][transport_client_worker][T#14]{New I/O worker #14}
elasticsearch[Blind Faith][transport_client_worker][T#15]{New I/O worker #15}
elasticsearch[Blind Faith][transport_client_worker][T#16]{New I/O worker #16}
elasticsearch[Blind Faith][transport_server_boss][T#1]{New I/O server boss #34}
elasticsearch[Blind Faith][transport_server_worker][T#1]{New I/O worker #18}
elasticsearch[Blind Faith][transport_server_worker][T#2]{New I/O worker #19}
elasticsearch[Blind Faith][transport_server_worker][T#3]{New I/O worker #20}
elasticsearch[Blind Faith][transport_server_worker][T#4]{New I/O worker #21}
elasticsearch[Blind Faith][transport_server_worker][T#5]{New I/O worker #22}
elasticsearch[Blind Faith][transport_server_worker][T#6]{New I/O worker #23}
elasticsearch[Blind Faith][transport_server_worker][T#7]{New I/O worker #24}
elasticsearch[Blind Faith][transport_server_worker][T#8]{New I/O worker #25}
elasticsearch[Blind Faith][transport_server_worker][T#9]{New I/O worker #26}
elasticsearch[Blind Faith][transport_server_worker][T#10]{New I/O worker #27}
elasticsearch[Blind Faith][transport_server_worker][T#11]{New I/O worker #28}
elasticsearch[Blind Faith][transport_server_worker][T#12]{New I/O worker #29}
elasticsearch[Blind Faith][transport_server_worker][T#13]{New I/O worker #30}
elasticsearch[Blind Faith][transport_server_worker][T#14]{New I/O worker #31}
elasticsearch[Blind Faith][transport_server_worker][T#15]{New I/O worker #32}
elasticsearch[Blind Faith][transport_server_worker][T#16]{New I/O worker #33}
elasticsearch[keepAlive/1.0.0.RC2]
Finalizer
Gang worker#0 (Parallel CMS Threads)
Gang worker#1 (Parallel CMS Threads)
Gang worker#0 (Parallel GC Threads)
Gang worker#1 (Parallel GC Threads)
Gang worker#2 (Parallel GC Threads)
Gang worker#3 (Parallel GC Threads)
Gang worker#4 (Parallel GC Threads)
Gang worker#5 (Parallel GC Threads)
Gang worker#6 (Parallel GC Threads)
Gang worker#7 (Parallel GC Threads)
JMX server connection timeout 80
Reference Handler
RMI Scheduler(0)
RMI TCP Accept-0
RMI TCP Connection(12)-172.20.10.4
Service Thread
Signal Dispatcher
Surrogate Locker Thread (Concurrent GC)
VM Periodic Task Thread
VM Thread

スレッド名が「elasticsearch」で始まるスレッドは62個あり、これがElasticsearchのアプリケーションに関連するスレッドになっています。これは、分かりやすいですね。
この一覧を眺めてみると、「http_server」「transport_client」「transport_server」という名称を含むスレッドが多いのが目立ちます。これは何でしょうか? Elasticsearchは通信周りのライブラリに、以前このブログでも紹介したNetty(ver.3)を利用しています。これらはNetty関連の通信スレッドになります。

「http_server」を含むスレッド

Elasticsearchに対するオペレーションは主にhttpで行います。Elasticsearchにアクセスするのにcurlを使った例をよく見ますが、その処理を行う箇所です。http通信周りのスレッドになります。

「transport_client」を含むスレッド

Elasticsearchがクラスタ間通信を行う際のクライアント側スレッドです。サーバ側と違い、こちらにはtimerスレッドがあります。

「transport_server」を含むスレッド

Elasticsearchがクラスタ間通信を行う際のサーバ側スレッドです。

3.Elasticsearchの入口

何か問題があったときには、ソースを読んだり、リモートデバッグすることもあると思います。そのための、入口になる箇所を探してみましよう。他のプロセスと通信処理を行っている箇所が分かれば、そこが入口になります。
Elasticsearchは通信処理にNettyを利用しています。Nettyの初期化は、org.jboss.netty.channel.ChannelPipelineFactoryを継承したクラスのgetPipelineメソッドで行います。このメソッドを実装している箇所を探してみましょう。
すると、以下のクラスが見つかりました。通信周りの設定を知りたい場合は、これらのソースコードを読むのが良いと思います。

org.elasticsearch.benchmark.transport.netty.NettyEchoBenchmark

これはElasticsearchのテストコードに入っているベンチマーク用クラスです。Elasticsearch本体とは関係ないですね。

org.elasticsearch.bulk.udp.BulkUdpService

bulk udp apiで利用される通信処理を初期化しているクラスです。この機能はデフォルトではdisabledとなっているため、先ほどのスレッド一覧にも登場していません。
リモートデバッグしたい場合は、BulkUdpServiceクラスの内部クラスHandlerのmessageReceivedメソッドにブレークポイントを設定してください。

org.elasticsearch.http.netty.NettyHttpServerTransport

Elasticsearchのhttp通信を初期化しているクラスです。内部クラスMyChannelPipelineFactoryで初期化しています。
リモートデバッグしたい場合は、org.elasticsearch.http.netty.HttpRequestHandlerのmessageReceivedメソッドにブレークポイントを設定してください。

org.elasticsearch.transport.netty.NettyTransport

Elasticsearchのクラスタ間通信を初期化しているクラスです。doStartメソッドの中でクライアント側、サーバ側の順に初期化しています。
リモートデバッグしたい場合は、org.elasticsearch.transport.netty.MessageChannelHandlerのmessageReceivedメソッドにブレークポイントを設定してください。クライアント側、サーバ側共に同じクラスを利用しますが、スレッド名が違うので区別できます。

4.Elasticsearchにリモートデバッグする方法

Windows版だとelasticsearch.batLinux版だとelasticsearch.in.shに、JVM引数を指定する変数JAVA_OPTSがあるので、ここにリモートデバッグに必要なオプションを追加しましょう。
例えばWindows版は、デフォルトで以下の設定になっています。

set JAVA_OPTS=%JAVA_OPTS% -XX:+HeapDumpOnOutOfMemoryError

次のようにデバッグオプションを追加しましょう。

set JAVA_OPTS=%JAVA_OPTS% -XX:+HeapDumpOnOutOfMemoryError -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8765

その後、以下の手順を実行します。

  • 「3.Elasticsearchの入口」に記載したブレークポイントを設定します。
  • Elasticsearchを起動します。
  • リモートデバッグで8765番ポートに接続します。

これで準備ができました。
例えば、curlでhttpリクエストを送信すると、HttpRequestHandlerのmessageReceivedメソッドに設定したブレークポイントで停止します。
 

さて、いかがだったでしょうか?
これをベースに、Elasticsearchのアクションやプラグインあたりの処理を追うと面白そうですね。
今回はElasticsearchの入口部分だけですが、理解やデバッグの参考になればと思います。

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Kibanaの画面をPDF出力しよう!

こんにちは! ツカノ(@)です。

最近、STAP細胞のニュースで賑わっていますね。iPS細胞と比較する記事が多いですが、たまにはES細胞のことも思い出してあげてください(ES細胞だってノーベル賞を取っているんですよ~)。まぁ、IT業界的にESと言えば、Elasticsearchのことを指す訳ですが、こちらはトレンドなプロダクトで、Marvelが登場したりと、まだまだ広がっていく感じがします。
f:id:acro-engineer:20140131080117p:plain

Elasticsearch+Kibanaは本当に便利で、様々な方が取り組まれており、情報量も急速に増えています。ログをElasticsearchに簡単に入れておくことができ、Kibanaを使って簡単に可視化できるのは良いですよね。
f:id:acro-engineer:20140131085246p:plain

さて、可視化したら、それを後から見ることができるようにファイル化したくなるのが人の心。Kibanaで表示した画面もファイルに落としたいしたいですよね。特に「PDFでレポート出力して報告」なんてケースもあるかと思います。

そこで、今回はKibanaのissue#509を参考に、Kibanaの画面をPDFに出力する方法を紹介します。

実行時の流れは、以下のようになります。

  • KibanaのURLを指定して、PNG形式に出力する。
  • PNG形式をPDF形式に変換する。

では、インストールから順に説明します。以下の内容は、Linux系OSで利用できます。cygwin等を利用することで、Windows系でも利用できるかもしれませんが、未確認です。

1.PhantomJSをインストール

WebKitを使ってスクリーンショット等ができるライブラリPhantomJSをインストールします。
これでスクリーンショットPNG形式で出力することができます。
f:id:acro-engineer:20140131080307p:plain

以下のページからダウンロードしてください。
 Download | PhantomJS

ここでは、phantomjs-1.9.7-linux-x86_64.tar.bz2をダウンロードして使います。

tar jxvf phantomjs-1.9.7-linux-x86_64.tar.bz2
sudo cp -p phantomjs-1.9.7-linux-x86_64/bin/phantomjs /usr/bin

また、以下のページに記載されているrasterize.jsを、PDF化するためのコマンドを実行するディレクトリに置きます。(Webから貼り付け、というのはイケてないですが)
 Utilize phantomjs to render screenshots (schedule email of dashboards) · Issue #509 · elasticsearch/kibana · GitHub
ただし、環境などによっては、rasterize.js内の変数waitTimeでページをオープンしてからファイル出力するまでの待ち時間(デフォルトは10,000ミリ秒)を調整した方が良いかもしれません。

2.sam2pをインストール

ラスターイメージをPDF形式に変換するためのライブラリsam2pをインストールします。
これで、PNG形式のファイルをPDF形式に変換することができます。

以下のページからダウンロードしてください。
 Downloads - sam2p - convert raster (bitmap) images to PostScript, PDF and other formats - Google Project Hosting

ここでは、以下の2つのファイルをダウンロードして使います。

  • sam2p-0.49-linux-static.tar.gz
  • tif22pnm-0.14.tar.gz
tar xvfz sam2p-0.49-linux-static.tar.gz
cd sam2p-0.49-linux-static
sudo cp -p sam2p /usr/local/bin
tar xvfz tif22pnm-0.14.tar.gz
cd tif22pnm-0.14
./configure
./do.sh compile
sudo cp -p png22pnm /usr/local/bin

3.Kibanaの背景設定

Kibanaのスタイルはデフォルトでdark(黒背景)のため、そのままPDFに出力するとあまりキレイではありません。Kibanaの設定画面からスタイルをlight(白背景)に設定した方が見やすいです。

Kibanaの画面の右上から設定画面を表示する。
f:id:acro-engineer:20140131084641p:plain

スタイルを「light」に変更する。
f:id:acro-engineer:20140131084657p:plain

4.PDF化の実行

以下のコマンドを実行することで、Kibanaの画面をPDF化することができます。直接PDF出力でなく、PNGファイルに出力してそこからPDFに出力しています。

phantomjs rasterize.js <PDF化するKibanaのURLを指定> sample.png "A4"
sam2p sample.png <PDFファイル名を指定>

これで、PDF出力できました。

OSについているスクリーンキャプチャ機能と違って、スクロールが必要な箇所もちゃんと出力されるのは良いですね。
Elasticsearch+Kibanaの組み合わせは可視化するには良いですが、レポート出力がまだ弱い感じがします。しっかりしたレポート機能が出て欲しいですが、現状は、このような工夫が必要ですね。
 

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Storm上で動作するオンライン機械学習ライブラリをOSSとして公開しました!

id:KenichiroMurata (@muraken720)です。

昨日、AcroquestはStorm上で動作するオンライン機械学習ライブラリをOSSとして公開しました!
ここでも簡単に紹介させて頂きます。

AcroMUSASHI Stream-ML(Machine Learning Library)

「AcroMUSASHI Stream-ML」はStorm上で動作し、連続的に発生し続けるストリームデータに対して、動的に学習データを更新しながら、リアルタイムの分析をすることができるライブラリです。

https://github.com/acromusashi/acromusashi-stream-ml

Storm をベースとした、ストリームデータの分散処理プラットフォームである「AcroMUSASHI Stream」と組み合わせることで、「データの収集~分析~結果の出力」までをシームレスに結合することができ、機械学習を行うためのシステム開発を迅速に行うことができます。

現在は以下のアルゴリズムが利用できます。

今後もニーズに合わせて、順次対応を広げていく予定です。
ぜひ、興味を持たれた方はご覧になってください。

参考


f:id:acro-engineer:20131211084154j:plain:w200

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ