コンパイル時に特定のAnnotationが付いているClassのmodifierをチェックする方法

先日、


という事例を聞いたので何とかコンパイル時に検知出来ないかぼーっと考えてて、プリプロセス的にチェックできないか思いついたので試してみました。

#ちなみにTweet上ではリフレクションとあったけど、多分実際はJavassistを使ってる気がする


今回サンプルとして動かしてみたのは、@Helloというannotationがついたクラスはstaticじゃないとエラーにするやつ。

org.komamitsu.pptest.annotation.Hello

package org.komamitsu.pptest.annotation;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {}

org.komamitsu.pptest.Main

package org.komamitsu.pptest;

import org.komamitsu.pptest.annotation.Hello;

public class Main {
    @Hello
    static class A {
    }
    
    @Hello
    class B {
    }

    public static void main(String argv[]) {
        System.out.println("helloworld");
    }
}

こんな感じになっている場合に、コンパイル時にclass Bをエラーとしたい。


で、プリプロセス的なタイミングでチェックするやつがこれ

org.komamitsu.pptest.preprocessor.Hello

package org.komamitsu.pptest.preprocessor;

import java.util.Set;

import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.annotation.processing.*;
import javax.tools.Diagnostic.*;

@SupportedAnnotationTypes(value= {"*"})
public class Hello extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element elm : roundEnv.getElementsAnnotatedWith(org.komamitsu.pptest.annotation.Hello.class)) {
            Set<Modifier> modifiers = elm.getModifiers();
            if (!modifiers.contains(Modifier.STATIC)) {
                processingEnv.getMessager().printMessage(Kind.ERROR, "@Hello: Only accepts static class. " + elm);
            }
        }
        return true;
    }
}

一応、これも。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.komamitsu</groupId>
  <artifactId>pptest</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Main classとかをコンパイルするときにorg.komamitsu.pptest.preprocessor.Helloを挟んであげれば良さそうなのだけど、スマートな方法が思いつかず以下のような手順で...

$ pwd
/Users/komamitsu/Dev/workspace/pptest
$ mvn clean package
    :
$ cd src/main/java/
$ javac -cp ../../../target/pptest-0.0.1-SNAPSHOT.jar -processor 
error: @Hello: Only accepts static class. org.komamitsu.pptest.Main.B
1 error

本当はちょこっとwarningが出てるんだけど、見辛いし本筋と関係ないので省略。

とまあ、こんな感じでコンパイル時にチェックできそうなんだけども、これをどうmsgpackに組み込むか(ユーザーが特に何もせずに当該チェックが作動するか)が思いついてない...

Raspberry PiでFluentdを立ち上げてTDにデータを流してみた話

一ヶ月程前に同僚である @doryokujin 先生からRaspberry Pi(面倒なので以下Pi)を頂いたのだけど、Piに必要なSDカードが手元に無くてすっかり放置していたのですが、満を持してSDカードを買ってきたのでちょっとPiを立ち上げてみた。という話を備忘録的に。
 
必要なもの(というかこれで間に合った、的なもの)

  • USBケーブル
  • RCAケーブル
  • USBキーボード
  • LANケーブル
  • SDカード(SDHD 8GB)

 
startxとか叩かなければUSBマウスは要らない。
 

Piの立ち上げ

  • 準備に使った環境はMac OX 10.8.5
  • SDカードは挿してある状態
  • SDカードの/dev/disk番号を確認してunmount
$ diskutil list
/dev/disk0
     :
/dev/disk4
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *7.8 GB     disk4
   1:                 DOS_FAT_32 NO NAME                 7.8 GB     disk4s1
$ diskutil unmountDisk /dev/disk4
Unmount of all volumes on disk4 was successful

 

  • OSにはRaspbianを選択。2014-01-07-wheezy-raspbian.zip を落としてきて展開したimgファイルをddで書き込み。かなり時間がかかる
$ sudo dd bs=1m if=~/Downloads/2014-01-07-wheezy-raspbian.img of=/dev/disk4
Password:
2825+0 records in
2825+0 records out
2962227200 bytes transferred in 1071.833726 secs (2763700 bytes/sec)
  • ddでイメージが書き込まれたSDカードをPiに挿してMBPとか適当なやつからUSBケーブルをつなげて電源を供給して上げれば起動
  • 自宅にディスプレイが無いので、あまり使われていないアナログテレビを用いて動作確認。RCAケーブルの黄色いやつ(映像用)をつなげれば滲んだ雰囲気の画面で確認出来る
  • Setting画面が表示されてユーザーのパスワードの変更とか色々できるので、やりたい場合はやる感じで。SSHの設定に関しては失敗してたけどひとまず先に進む
  • Settingを終えるとlogin promptが表示されるのでlogin。sshdも起動していたのでログインしてみたら普通に入れた。
  • Rubyは1.9.3, Javaは1.7が入っていて良い感じ

 

Fluentdとかを入れてTDにデータを流し込んでみる

  • Fluentdの安定版であるtd-agent(http://docs.fluentd.org/articles/install-by-deb)入れようかと思ったけど、何となくgemから入れてみた。FluentdはC拡張のビルドが発生するので、ruby-dev的なものを入れておく
$ sudo aptitude install ruby-dev
$ sudo gem install fluentd
  • TreasureDataにデータを入れるのでfluent-flugin-tdも。というか事前にTDのアカウントが必要なので適当に作っておくのが前提
$ sudo fluent-gem install fluent-plugin-td
  • fluentdのconfファイルを作成(/home/pi/fluentd/fluent.confとか)。入力はin_forwardだけの超シンプルな設定。出力はout_td。
<match td.*.*>
  type tdlog
  apikey <replace this with your TD API key!>
 
  auto_create_table
  buffer_type file
  buffer_path /home/pi/fluentd/td
</match>
 
<source>
  type forward
</source>
  • fluentd起動
$ fluentd -c /home/pi/fluentd/fluent.conf

fluentdデーモンとして起動したい場合は以下のようにしても良いかも。

$ export FLUENTD_ROOT=/home/pi/fluentd
$ fluentd -c $FLUENTD_ROOT/fluent.conf --log $FLUENTD_ROOT/fluentd.log --daemon $FLUENTD_ROOT/fluentd.pid
  • fluent-catとかfluent-postコマンドなどでレコードを放り込んでみる
$ echo '{"name":"pi", "age":49}' | fluent-cat td.pidb.test
  • td tablesコマンドで件数を確認
$ td tables pidb
+----------+-------+------+-------+--------+---------------------------+---------------------------+-----------------------+
| Database | Table | Type | Count | Size   | Last import               | Last log timestamp        | Schema                |
+----------+-------+------+-------+--------+---------------------------+---------------------------+-----------------------+
| pidb     | test  | log  | 1     | 0.0 GB | 2014-02-15 23:17:20 +0900 | 2014-02-15 23:14:15 +0900 | age:long, name:string |
+----------+-------+------+-------+--------+---------------------------+---------------------------+-----------------------+
1 row in set
  • td query コマンドでクエリーを投げてみる
$ td query -w -d pidb 'select name, age from test'
   :
Status     : success
Result     :
+------+-----+
| name | age |
+------+-----+
| pi   | 49  |
+------+-----+
1 row in set

 
見返してみると、記事にするほどの内容でも無い気がしてきたけど、勢いで書いたのでまぁ良いのではと。
 
PiにはGPIOとか付いているのでセンサー的なものをくっつけて、そこからの情報をTDに送り続けるというのは割と簡単に出来そう。データ量をあまり気にせずどんどん放り込んで後で集計の切り口を試行錯誤しやすいのは、1TDユーザーとしても使っていてかなり楽なので相性が良さげ。

fluent-logger について

こんにちは。Fluentd Advent Calendar 11日目担当の @komamitsu です。ワイワイ。

Fluentd Advent Calendarにノリで登録したもののfluentd本体やプラグインに余り関わっていないので途方に暮れていましたが、fluent/fluent-logger-java のメンテナンスをしていることもあり、fluent-logger周りのことを書いてやりすごしたいと思います。

fluent-logger-javaの一年

地味な見出しですね。自分でも書いてみて素でちょっと引きましたが、とりあえずfluent-logger-javaに関連した出来事などを...

いくつか細かいバグの修正や改善を入れていますが、その中でもTCP切断後の再接続処理やタイムアウトの問題が改善されているので、地味に使いやすくなっていると思います。

MLより。複数のfluentdに対して障害時のfailover、またはメモリバッファが一杯になったらファイル書き出し出来ないの?fluentdのメンテ中にメモリバッファがあふれちゃうよ的な話。確かに一理ある要望(まだ対応できてませんが...)

実のところ、TDでは全てのサーバーにfluentdがいて、アプリからは自サーバーのfluentdに投げているのであまり接続周りで困ったことがないんですよね... なので違う構成で利用されている方で困っているケースがあれば色々と知りたいなぁと思ってる次第です。

あとJavaのlog frameworkとの連携に関する要望とかもたまに目にしますねぇ。需要がどの程度あるのか気になります。

他のfluent-loggerとの比較

fluent-logger-javaは万一送信先のfluentdに送信出来なくてもメモリ上でバッファリングを行い、次回以降の送信成功時にまとめて送るようになっています。しかし、前述の "じゃあメモリバッファに書ききれなくなったらどうするの?ディスクに書き出すの?fluentdの接続先を変えてくれるの?" という要望には答えきれてないように思いました。そこで、他のfluent-loggerはどんな感じなのか気になったのでざっとコードを眺めてみました。

切断後の再接続の有無 再接続がexponential backoff? 送信失敗時のデータ保持 socket domain
Java fluent/fluent-logger-java yes yes yes INET
Ruby fluent/fluent-logger-ruby yes yes yes INET
Python fluent/fluent-logger-python yes no yes INET,UNIX
Perl fluent/fluent-logger-perl yes yes yes INET,UNIX
PHP fluent/fluent-logger-php yes yes no INET,UNIX
Node.js fluent/fluent-logger-node yes no yes INET
D fluent/fluent-logger-d yes yes yes INET
Scala oza/fluent-logger-scala yes yes yes INET
Haskell notogawa/fluent-logger-haskell yes no yes INET
OCaml fluent/fluent-logger-ocaml yes yes yes INET,UNIX
C roadman/fluent-logger-c no - no INET
C++ todayman/fluent-logger-cpp no - yes INET
C# zoetrope/fluent-logger-csharp yes no yes INET
Lua ngigroup-developer/fluent-logger-lua no - no INET

# Haskellもあったけど眠気の限界。気力があったら後で追加します...notogawaさんから教えて頂きHaskell版追記しました。thanks!

# 全体的にざっと眺めただけ&上記全ての言語についてちゃんと理解しているかというとそうではないので、間違っていたらご指摘頂けると幸いです。

全てのfluent-loggerでfluentdとの接続を保持しており、毎回接続しなおさないようになっていますが、それ以外は結構違いがあって面白いです。fluent-loggerのほうでどこまで頑張るか、呼び元のほうで頑張るかなど思想の違いもありそうです。(2013-12-11 思い出したので追記 ->) それ以外にも、Perl版ではfork()されたプロセスではsocketを共有せずに自分で開き直すような処理があったりして、そういう渋い工夫を見るのも楽しいですね。

ちなみに、複数のfluentdに接続してfailoverしたり、メモリバッファが溢れたらディスクに書き込む機能はどれもありませんでした。意外とそれで困るケースはすくないのかな。

一点気になったのが、いくつかのfluent-loggerではsocketへの書き込みのみを続けているっぽいので、fluentdが万一落ちたときにFINパケットが飛んで来ても気がつかないケースがありそうかなと。select()的なことをしてread()して0byteであったら切断を検知, またはnon-blockingでread()してEAGAINを期待する、ということが必要な気がしました。fluent-logger-rubyだと毎回別socketで接続しているようで、これであればFINを送る間もなく切断した場合でも検知出来るなー、でもちょっとコストが心配だなーと色々面白いです。

あと、自分では計測していないのですが、UNIX domainソケットのほうが性能が出るっぽい話も聞くので、優位な差がありそうであればJava版も対応してみても良いかな、などなど。

fluent-logger-ocaml

前述のfluent-logger軍団を見ていたらOCaml版が無いことに気がついたので、枠を埋める意味合いで作ってみました。

komamitsu/fluent-logger-ocaml

再接続の間隔をよろしくやってくれる機能はまだ付けてない(送信の度に毎回再接続を試みてしまう)のですが、それ以外はそれっぽく動くような気がします。あとテストが無くて意識の低さを露呈した感じになっています。MessagePackのシリアライズには mzp さんの msgpack/msgpack-ocaml を使わせて頂きました。型!


明日は katzchang さんです。

OCatraをLwt使うようにした

OCatraをいろいろ直したり、ベンチマークとってみたり - komamitsu.log で作っていたOCamlのおもちゃ的Webサーバー OCatraをLwtを使うように修正してみた。

1st 2nd 3rd 4th Ave.
OCatra (byte) 4238 4424 4299 4285 4311
OCatra (opt) 11806 12384 12381 12063 12158

bytecode版で1.5倍程度、native版で3倍程度高速になってうれしい。

nginxもついでに測ってみたのだけど、nginxのほうが倍近く速くて悔しい...

Androidでのnew String(byte[])とCharsetDecoder.decode()の違い

byte[] -&gt; String 変換のパフォーマンス、とAndroidでCharsetDecoder#decode(ByteBuffer in).toString()が遅い件 - komamitsu.logAndroidではnew String(byte)の方が速い結果が出たので[https://github.com/OESF/OHA-Android-4.0.3_r1.0:title=コード]を見てみた。

すると、CharsetDecoder.decode()ではICU Library(IBMの人が作ったunicode用ライブラリ)を使っているが、new String(byte, "UTF-8")の方は自前で手抜きしつつガリガリconvertしていた。なので、new String(byte)の方が高速で低機能なのではないかと思われ。

追記:
OpenJDK6のコードを見たら、CharsetDecoderの方はICUを使わずに自前でガリガリ。new String(byte)の方はCharsetDecoder#decodeを使ってた。

byte[] -> String 変換のパフォーマンス、とAndroidでCharsetDecoder#decode(ByteBuffer in).toString()が遅い件

Android上でMessagePackのベンチマークコードを動かしつつprofile取ってみたら、msgpack-java/src/main/java/org/msgpack/unpacker/StringAccept.java at master · msgpack/msgpack-java · GitHub辺り(CharsetDecoder#decode(ByteBuffer in).toString())でほとんどの時間が掛かっているなぁ、という感じ。

で、そもそも new String(byte[] byte, String charsetName) と CharsetDecoder#decode(ByteBuffer in).toString() でどれくらい性能があるのか気になったので計測してみた。確か、new String(...) の内部で CharsetDecoder#decode() してるというのは以前調べたことがあったので、今回はどの位違ってくるのか?という点に着目。

  public static void main(String[] args) throws IOException {
    String src = "あいうえおアイウエオアイウエオaiueoAIUEO";
    byte[] raw = src.getBytes();
    ByteBuffer byteBuffer = ByteBuffer.wrap(raw);
    Charset cs = Charset.forName("UTF-8");
    int until = 10000000;

    long start = System.currentTimeMillis();
    for (int i = 0; i < until; i++) {
      cs.newDecoder().decode(byteBuffer).toString();
    }
    System.out.println("CharsetDecoder.decode(): " + (System.currentTimeMillis() - start));

    start = System.currentTimeMillis();
    for (int i = 0; i < until; i++) {
      new String(raw, "UTF-8");
    }
    System.out.println("String(byte[]): " + (System.currentTimeMillis() - start));
  }

こんなのを書いて動かして見たところ...

CharsetDecoder.decode(): 720
String(byte[]): 2367

ざっと3倍位new String(byte[] byte, String charsetName)のほうが遅かった。OpenJDK 1.6でも大体同じ。

I/XXXX    (28542): CharsetDecoder.decode(): 4366
I/XXXX    (28542): String(byte[]): 1471

同等のコードをDalvik VM上で動かしたら(ループの回数は1/100に減らした)、結果が逆転した。ちょうど三倍程度、CharsetDecoder.decodeの方が遅くなった。

この辺がmsgpack-javaAndroid上で動かすと遅くなる件と関係あるのかも。ないのかも。

型変換テンプレートで変換する際、中間オブジェクトを生成しないようにした

Android上でJSONとMessagePackの簡単なベンチマークをとってみた - komamitsu.log の続き。

http://syuki.skr.jp/files/201204041/furuhashi-master-last-iso-pdfa.pdf を読んだところ、Unpacker.read(Class)は中間的な動的型付けオブジェクトを生成しないとのことなので、こちらを使うように修正してみた。

use unpacker.read(Class<T>) · ea24660 · komamitsu/AndroidMsgpackBenchmark · GitHub

Android2.2 Android4.2
Array S JSON 6421 2818
Msgpack 14636 1121
Array D JSON 1423 597
Msgpack 16284 6300
Msgpack: Unpacker.read(Class) 7458 4167
Map S JSON 3282 1894
Msgpack 19394 1117
Map D JSON 1044 614
Msgpack 17350 7754
Msgpack: Unpacker.read(Class) 8676 5223

結果は、デシリアライズの速度が前回のJSON:Msgpack=10~17:1という結果から, JSON:Msgpack=8:1と改善。