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でプロセスが殺されてしまうことがあるとのことです。
この辺の仕様周りについては軽く調べてみたのですが、あまり情報がなくいまいちよく分かっていません。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です。なお、この修正は次回のリリースに含まれる予定のようです。
// 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
embulkからelasticsearch2系にデータを流す
この記事はembulk-output-elasticsearchプラグインが正式に2系をサポートする前にとりあえずlocalで試したいという人向けです。
追記
以下の記事のやり方の方がスマートだったので載せておきます。
追記ここまで
fileから大量データをelasticsearchに読み込ませるためにembulkを使います。
install embulk
linux環境でQuick Start通りにやってすんなりと入りました。
$ 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プラグインは以下のものを使います。
現在、リリースされている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タブに表示されます。
superagentのjsonpプラグインをnpmで作った
何か書いてみたいけど書くことがなかったのでしばらく前に作ったsuperagentのjsonpプラグインを作った話をしてみたいと思います。
先に作ったものを貼っておきます。
superagent
superagentはjavascriptのajaxライブラリでシンプルなメソッドチェインで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モジュールは既に別のものがありました。
しかし、「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の特定の変数に値が一定時間経過後も入らなかった場合はエラーとして扱うようになっています。
npmモジュール周り
npmモジュールの作成に関しては以下の記事などわかりやすい記事がたくさんあったので全く困りませんでした。ありがとうございます。
その他、Travisやtestに使ったモジュールも初めてのものが多かったので書こうかと思いましたが大した内容でもないのに話が右往左往してしまいそうなのでこの辺で終了したいと思います。