ver4.2以降のkibanaが突然死する問題について

追記

2015/12/17に問題がないバージョン (4.1.4 / 4.2.2 / 4.3.1) がリリースされました!

追記ここまで

2016/07/27 追記

Segmentation faultsでプロセスが落ちる

本番サーバも同様に250MB割当でしばらく運用していた所、頻繁にプロセスが落ちるようになりました。 ログ上にSegmentation faultsとだけ表示されなんの前触れもなく落ちるので困っていました。

当初は自動再起動等で対処していたのですが、原因はメモリ割当量が少ないことにあることが分かり2GBまであげた所全く落ちなくなりました。 リソース監視を見ると500MB程度までは使うことがあるようなので最低500MB-1GBは割当てた方が良いかもしれません。

2016/07/27 追記ここまで

最近ようやくサーバ周りをやらせてもらえるようになってきたのでとても楽しいです。

kibanaのプロセスが突然死する問題

今回はver4.2以降のkibanaのプロセスが突然死する問題についてです。

結論から言うとこの問題は仮想化環境などのメモリ容量が低いサーバにおいてのみ発生する問題のようです。というのもnode *1 は実行環境におけるメモリ上限を検知しておらず、設定値によって決められた値でGCを行うという仕様らしいです。この設定値のdefault値が1.5GB *2 になっているため、1.5GBよりも使えるメモリ容量が少ないサーバ上で動かしているとnodeのGCが起きる前にOOMでプロセスが殺されてしまうことがあるとのことです。

github.com

devcenter.heroku.com

この辺の仕様周りについては軽く調べてみたのですが、あまり情報がなくいまいちよく分かっていません。nodeにの --v8-options にあるそれっぽい値について調べてみても default: 0 になっていました。

$ node --v8-options | grep old_space_size -A 1
  --max_old_space_size (max size of the old space (in Mbytes))
        type: int  default: 0

解決策

解決策としては、実行ファイルのnode呼び出し箇所を $NODE_OPTIONS を渡すように修正して、外から --max-old-space-size を渡してあげるように修正してあげればOKです。なお、この修正は次回のリリースに含まれる予定のようです。

github.com

bin/kibana (for Linux/Mac)

// 24行目
-exec "${NODE}" "${DIR}/src/cli" ${@}
+exec "${NODE}" $NODE_OPTIONS "${DIR}/src/cli" ${@}

bin/kibana.bat (for Windows)

// 21行目
-"%NODE%" "%DIR%\src\cli" %*
+"%NODE%" %NODE_OPTIONS% "%DIR%\src\cli" %*

実行時に--max-old-space-sizeを付与

ちなみに、適切値については議論の対象となっており(まだ始まってませんが)、とりあえず250をdefaultにするPRがあるのでその辺りを目安にすればいいのではないかと思います。

NODE_OPTIONS="--max-old-space-size=250" bin/kibana

https://github.com/elastic/kibana/issues/5595 https://github.com/elastic/kibana/issues/5170#issuecomment-157656028

*1:というよりV8エンジンのa lazy and greedy garbage collectorという機能のようです

*2:nodeのversionによって差異はあるかもしれない

embulkからelasticsearch2系にデータを流す

この記事はembulk-output-elasticsearchプラグインが正式に2系をサポートする前にとりあえずlocalで試したいという人向けです。

追記

以下の記事のやり方の方がスマートだったので載せておきます。

qiita.com

追記ここまで

fileから大量データをelasticsearchに読み込ませるためにembulkを使います。

install embulk

linux環境でQuick Start通りにやってすんなりと入りました。

github.com

$ curl --create-dirs -o ~/.embulk/bin/embulk -L "http://dl.embulk.org/embulk-latest.jar"
$ chmod +x ~/.embulk/bin/embulk
$ echo 'export PATH="$HOME/.embulk/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc

$ embulk --version
embulk 0.7.10

embulk-output-elasticsearch

elasticsearchプラグインは以下のものを使います。

github.com

現在、リリースされているv0.1.8はelasticsearch v1.5.2を使っており、2系に対応していません。 リポジトリには2系対応がコミットされているのでとりあえず試したい場合はそれを使うとよいです。

$ embulk gem install embulk-output-elasticsearch
$ cd ~/.embulk/jruby/2.2.0/gems/embulk-output-elasticsearch-0.1.8
$ git init
$ git remote add origin https://github.com/muga/embulk-output-elasticsearch.git
$ git pull origin master
$ ./gradlew build
$ ./gradlew classpath

example

embulkではexample用のデータセットが簡単に得られるのでそれで試してみます。

$ embulk example ./try1
$ embulk guess   ./try1/example.yml -o config.yml
$ embulk preview config.yml

設定の一部をelasticsearch向けのものに変更します。

in:
  type: file
  path_prefix: /home/vagrant/try1/csv/sample_
  decoders:
  - {type: gzip}
  parser:
    charset: UTF-8
    newline: CRLF
    type: csv
    delimiter: ','
    quote: '"'
    trim_if_not_quoted: false
    skip_header_lines: 1
    allow_extra_columns: false
    allow_optional_columns: false
    columns:
    - {name: id, type: long}
    - {name: account, type: long}
    - {name: time, type: timestamp, format: '%Y-%m-%d %H:%M:%S'}
    - {name: purchase, type: timestamp, format: '%Y%m%d'}
    - {name: comment, type: string}
out:
  type: elasticsearch
  nodes:
  - {host: localhost, port: 9300}
  cluster_name: your-cluster-name
  index: try1
  index_type: test

これを実行してみます。

$ embulk run config.yml
2015-12-07 18:10:27.686 +0900: Embulk v0.7.10
2015-12-07 18:10:31.394 +0900 [INFO] (transaction): Loaded plugin embulk-output-elasticsearch (0.1.8)
2015-12-07 18:10:31.524 +0900 [INFO] (transaction): Listing local files at directory '/home/vagrant/try1/csv' filtering filename by prefix 'sample_'
2015-12-07 18:10:31.532 +0900 [INFO] (transaction): Loading files [/home/vagrant/try1/csv/sample_01.csv.gz]
2015-12-07 18:10:31.777 +0900 [INFO] (transaction): [Luke Cage] loaded [], sites []
2015-12-07 18:10:32.986 +0900 [INFO] (transaction): {done:  0 / 1, running: 0}
2015-12-07 18:10:33.006 +0900 [INFO] (task-0000): [Air-Walker] loaded [], sites []
2015-12-07 18:10:34.031 +0900 [INFO] (task-0000): Execute 4 bulk actions
2015-12-07 18:10:34.564 +0900 [INFO] (elasticsearch[Air-Walker][listener][T#1]): 4 bulk actions succeeded
2015-12-07 18:10:34.665 +0900 [INFO] (transaction): {done:  1 / 1, running: 0}
2015-12-07 18:10:34.678 +0900 [INFO] (main): Committed.
2015-12-07 18:10:34.678 +0900 [INFO] (main): Next config diff: {"in":{"last_path":"/home/vagrant/try1/csv/sample_01.csv.gz"},"out":{}}

無事、try1にデータが入りました。

※ちゃんと使う場合はelasticsearchプラグインがリリースされるまで待った方が良いと思います。

AndroidのInstrumented Unit Testsを試す

試したのはAndroidの公式の以下のページの内容になります。

Building Instrumented Unit Tests | Android Developers

準備

基本的には手順通りにやれば問題なく出来ます。

  • Android Testing Support Libraryをインストール
  • 画面左下のBuild Variantsタブを選択し、Test ArtifactをAndroid Instrumentation Testsに変更しておく
  • app/src/androidTest/javaディレクトリを作成
  • moduleのbuild.gradleにdependenciesを追記
    • 僕の環境だとhamcrestが入っているとダメでした *1
dependencies {
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile 'com.android.support.test:rules:0.3'
    // Set this dependency if you want to use Hamcrest matching
    androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
}
  • 同じくmoduleのbuild.gradle内のtestInstrumentationRunnerをAndroidJUnitRunnerに設定する
android {
    defaultConfig {
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

実装

テストクラスは以下のように実装します。
Contextは InstrumentationRegistry から取得出来るようです。

package com.example;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import junit.framework.Assert;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class SampleTest {

    @Test
    public void testSample() {
        // Contextの取得
        Context c = InstrumentationRegistry.getContext();

        Assert.assertEquals(2, 1 + 1);
    }

}

1つ注意したいのはAndroidJUnit4を使用する時はWorkerThreadで動作するようで、Looper.myLooper()がnullになっているので new Handler() 等のコードを通ると内部で以下のエラーが起きます。

Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)

WebViewなんかも初期化時にHandlerを生成するらしく、普通に生成しようとすると同様のエラーが出て生成出来ません。

対策としては、Handlerを使ってMainThreadで動作するようにしてあげればOKです。

Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
    @Override
    public void run() {
        // ここにテストコードを書く
    }
});

追記

InstrumentationクラスのrunOnMainSyncメソッドを使えば同じようなことが出来るみたいです。こちらだと同期で実行してくれるのでこのRunnableが終わるまで待ってくれるのでテストを書くならこちらの方がやりやすいかもしれません。

InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    @Override
    public void run() {
        // ここにテストコードを書く
    }
});

実行

実行はJUnitなどを実行する時と同様にテストしたい対象を右クリックしてAndroid TestのRun (ドロイド君のマークのRun) を実行します。

GenyMotionを使用している場合は、初期状態だとデバイスを立ち上げていても勝手にAndroidエミュレータが立ち上がるようになっていると思うので *2 、Run > Edit Configurations からTarget Deviceを Show chooser dialog に設定しておくと良いと思います。

テスト結果はJUnitのLocal Testingと同様にRunタブに表示されます。

*1:JUnit4のLocal Testingと併用していたからかもしれません

*2:僕の環境ではそうでした

superagentのjsonpプラグインをnpmで作った

何か書いてみたいけど書くことがなかったのでしばらく前に作ったsuperagentのjsonpプラグインを作った話をしてみたいと思います。

先に作ったものを貼っておきます。

www.npmjs.com

superagent

superagentはjavascriptajaxライブラリでシンプルなメソッドチェインでajaxリクエストを送ることの出来るライブラリです。

SuperAgent - Ajax with less suck

request
  .post('/api/pet')
  .send({ name: 'Manny', species: 'cat' })
  .set('X-API-Key', 'foobar')
  .set('Accept', 'application/json')
  .end(function(err, res){
    if (res.ok) {
      alert('yay got ' + JSON.stringify(res.body));
    } else {
      alert('Oh no! error ' + res.text);
    }
  });

なぜ作ったのか

superagentはajaxリクエストを送るライブラリなのでJSONPってそもそも?って感じですが、サーバに対するリクエストの書式を統一できるので(多分)多少メリットはあるのではないかなと思います。

superagentでjsonpを送ることの出来るnpmモジュールは既に別のものがありました。

www.npmjs.com

しかし、「1. エラーハンドリングが出来ない、 2. superagentのplugin機構に則っていない *1、3. ただnpmモジュールを作ってみたかった」という理由から自分で作ってみることにしました。

実装

前項でも書きましたが、superagentにはplugin機構があります。 ちなみに、これはrequest変数を関数に渡してメソッドチェイン出来るようにしてくれているだけのものです。

Request.prototype.use = function(fn) {
  fn(this);
  return this;
}

superagent-jsonpxもこのplugin機構に合うように作りました。
使い方としては以下のように使います。

var request = require('superagent');
var jsonp = require('superagent-jsonpx');
 
var options = { timeout: 3000, callbackKey: 'cb' };
 
request.get(uri)
  .use(jsonp(options))
  .end(function (err, res) {
    if (!err && res.body) {
      // request success 
      console.log(res.body);
    } else {
      // request timeout (or error) 
    }
  });

jsonp 関数に options を渡すとrequestを受け取る関数を返しています。 superagentで実際にajaxのリクエストが送られるのはendメソッドの中なのでプラグイン内でendメソッドをオーバーライドしてjsonpリクエストを送るようにしました。

ちなみにjsonpでは基本的にはエラーは取得出来ませんが、以下のページに書いてあったiframeを使う方法でエラーをハンドリングするようにしました。具体的にはiframeの特定の変数に値が一定時間経過後も入らなかった場合はエラーとして扱うようになっています。

d.hatena.ne.jp

npmモジュール周り

npmモジュールの作成に関しては以下の記事などわかりやすい記事がたくさんあったので全く困りませんでした。ありがとうございます。

qiita.com

その他、Travisやtestに使ったモジュールも初めてのものが多かったので書こうかと思いましたが大した内容でもないのに話が右往左往してしまいそうなのでこの辺で終了したいと思います。

*1:今見たらsuperagent-jsonpでもver0.0.6からplugin機構に則るようになったみたいです