tokuhirom's blog

GraalVM の native-image で HelloWorld してみる

目的

https://medium.com/graalvm/instant-netty-startup-using-graalvm-native-image-generation-ed6f14ff7692 によると、graalvm を利用すると Java Application の起動が圧倒的に高速になるようだ。実際にどの程度高速化されるのかを Hello World Application を手元で実行してみることにより体感する。

実験

$ javac Hello.java
$ java Hello
Hello
$ time java Hello
Hello
java Hello  0.08s user 0.02s system 102% cpu 0.097 total
$ ~/Downloads/graalvm-1.0.0-rc1/Contents/Home/bin/native-image Hello
Build on Server(pid: 55301, port: 26681)*
   classlist:     918.63 ms
       (cap):   1,817.31 ms
       setup:   2,839.95 ms
  (typeflow):   3,744.88 ms
   (objects):   2,570.67 ms
  (features):      43.37 ms
    analysis:   6,469.76 ms
    universe:     281.22 ms
     (parse):   1,153.94 ms
    (inline):   1,592.75 ms
   (compile):  10,151.29 ms
     compile:  13,517.63 ms
       image:   2,241.96 ms
       write:   1,410.72 ms
     [total]:  27,753.54 ms
$ ls -lah
total 5.0M
drwxr-xr-x   5 tokuhirom staff  160 May 23 11:14 ./
drwxr-xr-x 121 tokuhirom staff 3.8K May 23 11:11 ../
-rw-r--r--   1 tokuhirom staff  401 May 23 11:13 Hello.class
-rw-r--r--   1 tokuhirom staff  111 May 23 11:13 Hello.java
-rwxr-xr-x   1 tokuhirom staff 5.0M May 23 11:14 hello*
$ time ./hello
Hello
./hello  0.00s user 0.00s system 65% cpu 0.011 total
$ file ./hello
./hello: Mach-O 64-bit executable x86_64

結論/考察

結論からいうと、Hello World レベルのプログラムでも起動が高速化されている。ファイルサイズは 5MB 程度。 この速度ならば、日常的に利用する command line application を Java で記述することも現実的といえる。

Created: 2018-05-23 02:20:02 +0000
Updated: 2018-05-23 02:20:02 +0000

commons-math3 の二次元行列用の型についてのメモ

BlockRealMatrix

キャッシュフレンドリーで速いらしい。

Array2DRowRealMatrix

double[][] で表現されるひじょうに素直な実装

DiagonalMatrix

対角行列用の型。対角行列を扱うならコレを使うとメモリを節約できたりする。

OpenMapRealMatrix

open addressed map ベースの実装。sparse な行列ならコレを使うとよい。

初期化コストに関するベンチマーク

雑ですが。。

    @Test
    fun bench() {
        val tries = 10000000

        val startA = System.currentTimeMillis()
        for (i in 0 until tries) {
            Array2DRowRealMatrix(11, 11)
        }
        val endA = System.currentTimeMillis()
        val startB = System.currentTimeMillis()
        for (i in 0 until tries) {
            BlockRealMatrix(11, 11)
        }
        val endB = System.currentTimeMillis()
        println("A=${endA-startA} B=${endB-startB}")
    }

で、

A=6992 B=2435

みたいな感じ。

Created: 2018-05-14 02:10:21 +0000
Updated: 2018-05-14 02:10:21 +0000

commons-math3 の BetaDistribution を利用する場合は rng をキャッシュする

commons-math3 の BetaDistribution を利用する場合、alpha/beta が変わるたびに new BetaDistribution( alpha, beta) とかしてはいけない。 new BetaDistribution(rng, alpha, beta, 1.0) は `new BetaDistribution(new Well19937c(), alpha, beta) ということになるからだ。

new Well19937c() は乱数生成系だが、この初期化処理は重いので、キャッシュしないと非常に処理が重くなる。

Created: 2018-04-10 01:59:37 +0000
Updated: 2018-04-10 01:59:37 +0000

nuxtjs で bootstrap 使いたい

nuxt.js で twitter bootstrap を有効にする方法

https://bootstrap-vue.js.org/docs/#nuxt-js を参考にやれば良い。

npm i bootstrap-vue --save

して、nuxt.config.js に以下を追加。

{
  modules: [
    'bootstrap-vue/nuxt',
  ]
}
Created: 2018-03-27 02:47:07 +0000
Updated: 2018-03-27 02:47:07 +0000

Host の canonical name を取得するワンライナー

python -c "import socket; import sys; print(socket.getaddrinfo(sys.argv[1], 0, socket.AF_INET, 0, socket.IPPROTO_TCP, socket.AI_CANONNAME)[0][3])" YOUR_HOST_NAME
Created: 2018-03-06 04:19:31 +0000
Updated: 2018-03-06 04:19:31 +0000

spring boot2 で redis 使った session を使う方法

Created: 2018-03-05 04:57:06 +0000
Updated: 2018-03-05 04:57:06 +0000

lombok 1.16.20 以後で @lombok.Value された値を Jackson で deserialize しようとするとエラーになる

@lombok.Value された bean に対して ObjectMapper で deserialize した場合、以下のようなエラーがある。

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `MyGreatResponse` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (String)"{"FOO":"BAR"}"; line: 1, column: 2]

これは lombok 1.16.20 で以下の変更が入ったためである。

BREAKING CHANGE: lombok config key lombok.anyConstructor.suppressConstructorProperties is now deprecated and defaults to true, that is, by default lombok no longer automatically generates @ConstructorProperties annotations. New config key lombok.anyConstructor.addConstructorProperties now exists; set it to true if you want the old behavior. Oracle more or less broke this annotation with the release of JDK9, necessitating this breaking change.

https://projectlombok.org/changelog

lombok 1.16.20 で、immutable 厨したいときは lombok.config に以下のように記述すべし。

lombok.anyConstructor.addConstructorProperties= true
Created: 2018-03-01 10:32:41 +0000
Updated: 2018-03-01 10:32:41 +0000

spring boot 2RC2 で prometheus を actuator で export させるやり方

    compile("org.springframework.boot:spring-boot-starter-actuator")
    compile("io.micrometer:micrometer-registry-prometheus")

を build.gradle に追加

management:
  endpoint:
    prometheus:
      enabled: true
  endpoints:
    web:
      exposure:
        include: info,health,prometheus

を設定に追加。

Created: 2018-02-25 14:13:43 +0000
Updated: 2018-02-25 14:13:43 +0000

Ruby の deserializer の速度比較

ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16]

require 'msgpack'
require 'benchmark'
require 'json'

# https://github.com/fluent/fluentd/blob/master/lib/fluent/plugin/parser_ltsv.rb
class LTSV
  def self.parse(text)
    r = {}
    text.split("\t").each do |pair|
      key, value = pair.split(":", 2)
      r[key] = value
    end
    r
  end

  def self.generate(data)
    data.map {|k,v| k+":"+v }.join("\t")
  end
end

src = Hash[(1..1000).map{|i| i.to_s}.each_slice(2).to_a]

msgpk = src.to_msgpack
json = JSON.generate(src)
ltsv = LTSV.generate(src)

iterations = 100_000

puts "msgpk=#{msgpk.length} bytes"
puts "json=#{json.length} bytes"
puts "ltsv=#{ltsv.length} bytes"
puts ""

Benchmark.bm(10) do |x|
  x.report('msgpack') do
    iterations.times { MessagePack.unpack(msgpk) }
  end
  x.report('json') do
    iterations.times { JSON.parse(json) }
  end
  x.report('ltsv') do
    iterations.times { LTSV.parse(ltsv) }
  end
end
msgpk=3896 bytes
json=5894 bytes
ltsv=3892 bytes

                 user     system      total        real
msgpack     11.390000   0.050000  11.440000 ( 11.509034)
json        31.690000   0.140000  31.830000 ( 32.131092)
ltsv        36.870000   0.270000  37.140000 ( 37.986320)

そんな感じで。

Created: 2018-01-31 10:16:43 +0000
Updated: 2018-01-31 10:16:43 +0000

Capturing grafana dashboard and post it to LINE group using LINE Notify

I want to post a screenshot of the Grafana dashboard to LINE Notify.

Grafana distribution includes PhantomJS( Alerting feature uses PhantomJS. https://github.com/grafana/grafana/blob/master/pkg/services/alerting/notifier.go#L96 ).

Then, you can take a snapshot using curl command.

You need to rewrite Grafana dashboard URL. Insert /render prefix for your dashboard URL. For example, when your dashboard URL is http://grafana.example.com/dashboard/db/my-service, PhantomJS URL is http://grafana.example.com/render/dashboard/db/my-service.

If you want to hide a header, add ?kiosk query parameter for the URL.

Grafana supports API keys and Basic Auth. With curl, you can call it like e.g. curl -u 'YOUR_EMAIL:YOUR_PASSWORD' GRAFANA_URL.

It's summarized as follows:

curl  -u 'YOUR_EMAIL:YOUR_PASSWORD' http://grafana.example.com/render/dashboard/db/my-service?kiosk -o img.png

Post to LINE Notify

You can upload dashboard image to LINE Notify using curl command.

curl -X POST https://notify-api.line.me/api/notify 
       -H 'Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN' 
       -F 'message=test' 
       -F 'imageFile=@img.jpg'

See https://engineering.linecorp.com/ja/blog/detail/94 for more details.

SEE ALSO

http://moznion.hatenadiary.com/entry/2016/09/03/004038

Created: 2018-01-05 14:19:48 +0000
Updated: 2018-01-05 14:19:48 +0000

rust のベンチマーク取る時は `cargo build --release` しなくてはならない

[package]
name = "hello_world"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]

[dependencies]
futures = "0.1.14"
hyper = "0.11"

extern crate hyper;
extern crate futures;
use futures::future::Future;

use hyper::header::ContentLength;
use hyper::server::{Http, Request, Response, Service};

struct HelloWorld;
const PHRASE: &'static str = "Hello, World!";

impl Service for HelloWorld {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;

    type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;

    fn call(&self, _req: Request) -> Self::Future {
        Box::new(futures::future::ok(
                    Response::new()
                    .with_header(ContentLength(PHRASE.len() as u64))
                    .with_body(PHRASE)
                    ))
    }
}

fn main() {
    let addr = "127.0.0.1:3000".parse().unwrap();
    let server = Http::new().bind(&addr, || Ok(HelloWorld)).unwrap();
    server.run().unwrap();
}

のようなシンプルな hyper の本家サイトから取ってきたコードを実行した場合について考える。

普通に cargo build すると依存ライブラリ含めてデバッグビルドされるのでとにかく遅い。

仮想マシンのローカルから適当にベンチマーク取った場合以下のようになる。

[tokuhirom@dev2 httpd]$ wrk --latency http://localhost:3000
Running 10s test @ http://localhost:3000
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.65ms    1.03ms  21.86ms   89.72%
    Req/Sec     3.16k   718.96     4.04k    61.50%
  Latency Distribution
     50%    1.33ms
     75%    1.61ms
     90%    2.70ms
     99%    5.65ms
  63230 requests in 10.05s, 5.37MB read
Requests/sec:   6293.04
Transfer/sec:    546.95KB

cargo build --release した Release ビルドだと以下のようになり、めちゃくちゃ差が出る。

[tokuhirom@dev2 httpd]$ wrk --latency http://localhost:3000
Running 10s test @ http://localhost:3000
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   353.40us  428.95us  12.69ms   98.36%
    Req/Sec    14.56k     4.55k   20.66k    45.77%
  Latency Distribution
     50%  249.00us
     75%  393.00us
     90%  538.00us
     99%    0.94ms
  291181 requests in 10.10s, 24.71MB read
Requests/sec:  28828.72
Transfer/sec:      2.45MB

C や C++ の場合には、ライブラリコードは yum で入れたものとかを使う事が多く、そこまでアプリケーションコードが debug build かどうかに神経質にならなくてもわりとそれなりにパフォーマンスに差は出にくいが、cargo の場合、依存ライブラリも debug build してるっぽいんでめっちゃ差がでるっぽさ。

ちなみに rust の http server 実装は実質的に hyper 一択かつ、hyper は express-worker 的な機構もないので自前でthread をなんとかしない限り CPU を使い切るのは難しそう。

Created: 2017-12-23 14:17:14 +0000
Updated: 2017-12-23 14:17:14 +0000

-Dsun.net.inetaddr.ttl=1 するよりも networkaddress.cache.ttl 使おう

jvm はデフォルトで DNS cache が 30sec かかっている。これはやや長過ぎると web service では判断されるケースが多い。 そこで、通常ではこれを短く設定して本番環境のデプロイをしている場合が多い。 手元のあるコードでは -Dsun.net.inetaddr.ttl=1 が指定されていたが、現在では -Dnetworkaddress.cache.ttl=1 を利用すべき。

https://www.glamenv-septzen.net/view/1346 このページが挙動について詳しい。 基本的に sun.net.inetaddr.ttl は歴史的経緯で指定されている名称であり、現代では networkaddress.cache.ttl を利用する方が良い。

Created: 2017-12-01 07:26:03 +0000
Updated: 2017-12-01 07:26:03 +0000

tableau server の "Query View Image" API のデフォルトキャッシュ期間は長い

http://onlinehelp.tableau.com/v10.3/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm%23Query_View_Image%3FTocPath%3DAPI%2520Reference%7C_____68

If you make multiple requests for an image, subsequent calls return a cached version of the image. This means that the returned image might not include the latest changes to the view. To decrease the amount of time that an image is cached, use tabadmin to reduce the value of the vizportal.rest_api.view_image.max_age setting. For more information, see tabadmin set options in the Tableau Server help.

で、これの default value は 720min=12hours なので、とにかくキャッシュが長い。

なんか cache 破棄させるほうほうもありそうなものだが、いまいちよくわからないので、とりあえずデフォルトのキャッシュ時間を短くして運用しましょうかというよう話になった。

Created: 2017-11-30 09:59:41 +0000
Updated: 2017-11-30 09:59:41 +0000

homebrew との付き合い方を見直す 2017

  • 最近の homebrew では、OS に含まれるコマンドも利用可能なので積極的に利用していく
    • なんとかenv 系の管理コマンドは、LL で本格的に開發している場合には便利だが、最近はバージョンを固定して開發するようなことが減ってるのと JVM 言語メインなので気にしないで brew install perl などする。
    • system perl 等にアプデさせると、
  • 古いバージョンのバイナリは brew cleanup で消せる
  • 最近は vim の拡張をゴリゴリ入れたりしてないんで、vim も OS 標準のものでいい
    • たぶん、lua を使うために brew で入れるようにしてたのだが、lua が必要になる理由の根源であった neonantoka シリーズを最近使ってないんで、もはや system vim で良い。
  • brew cask は積極的に利用している。

というわけで現在利用してるのは以下。 * ant(歴史的事情によりデプロイツールが ant に依存しており、動作確認のために手元に入れておく必要がある) * bvi(vi binding で利用できるバイナリエディタ。バイナリの中身をサッと見るときに便利) * cheat(ちょっとしたメモ代わりに使ってて便利) * coreutils(GNU コマンドに慣れすぎてるんで。。) * go * hub(github からの clone とかでたま〜につかう) * jq(json を prettify する用途ぐらいにしか使ってないけど) * maven * ngrep(packet を capture するときにめっちゃ使ってる) * node * peco * perl * protobuf * python3 * redis * rename * ruby * scala * sbt * tcping * tcptraceroute * thesilversearcher * thrift * sqlite * tree * tmux * w3m * wget(ファイルをダウンロードする時は wget のほうが楽な気がしてる) * wrk(最近は ab よりこっちメイン)

その他、brew cask で入れているものが以下。 * alfred(なんとなく継続しているが、真面目に設定してないので quick silver との違いは screen lock をショートカットキーでできることぐらい) * evernote(本来は web ui で十分という気もするが、web ui の出来が微妙なので一応入れてる) * firefox(最近アプデされたこともあり、久々に使ってみている) * filezilla(たまーに使う) * freemind(たまーに考えをまとめるときに使う) * google-chrome * google-japanese-ime * iterm2(選択範囲が iterm2 のほうが好み、という理由だけでなんとなく使い続けている) * java * java8(9 だと lombok が発狂するので。。) * karabiner(英かなちゃんと動くようになってよかったね、という感じ) * kindle * skitch * skype(最近は使ってない) * vagrant(最近は社内IaaS使う事が多くて、使ってない) * virtualbox * visualvm

Created: 2017-11-23 14:05:47 +0000
Updated: 2017-11-23 14:05:47 +0000

spring boot 2 にしたら `springBoot { executable = true }` が通らない

bootJar {
    launchScript()
}

にしましょう。

Created: 2017-11-21 02:55:57 +0000
Updated: 2017-11-21 02:55:57 +0000

spring boot 2 にしたら InvalidConfigurationPropertyNameException が発生するとき

spring boot 2 だと @ConfigurationProperties("fooBar") みたいなのを指定することができなくなる。 @ConfigurationProperties("foo-bar") なら通るので、変更必要。

Created: 2017-11-21 02:48:47 +0000
Updated: 2017-11-21 02:48:47 +0000

spring boot2 にしたら `bootRun { systemProperty 'spring.profiles.active', 'local' } }` が動かないよって場合

spring-boot 2 では以下の書き方は許容されなくなった。

bootRun {
  systemProperty 'spring.profiles.active', 'local'
}

以下のように変更する。

bootRun {
    execSpec {
        systemProperty 'com.example.foo', 'bar'
    }
}

https://twitter.com/ankinson/status/918498381768658944 ref. https://docs.spring.io/spring-boot/docs/2.0.0.M5/gradle-plugin/reference/html/#running-your-application

Created: 2017-11-21 02:22:37 +0000
Updated: 2017-11-21 02:22:37 +0000

/dev/urandom を fork して読み込んでもいいんかって話

tokuhirom /dev/urandom って開いたまま fork して読み込んだらまずいんですっけ?
親 process で /dev/urandom 開いたまま、fork してそれぞれの子プロセスで読んだら同じの取れる?
kazuho 同じの取れないですね
一般論なファイルについてもそう
つまり、open file descriptionは同一なので、readしたらその中にあるfile offsetは進むので (edited)
Created: 2017-11-13 03:32:48 +0000
Updated: 2017-11-13 03:32:48 +0000

spring-boot-actuator 使いたいけど spring-webmvc はいらないよってケース

management.port のほうは開きたいけど server.port のほうは開きたくないってときは

server.port: -1

とかやっときゃいいとのこと。

Created: 2017-11-08 05:39:24 +0000
Updated: 2017-11-08 05:39:24 +0000

Perl Web application に於ける Data::UUID の利用について

本文書について

本文書では Perl における Data::UUID での UUID 生成について議論する。 特に、Linux 環境に於ける Data::UUID->new->create_str の挙動について考察する。

Data::UUID について

Data::UUID は Alexander Golomshtok 氏が開発したモジュールで、メンテナンスされなくなった結果として、現在は rjbs 氏がパッチの適用などの消極的なメンテナンスを実施している。

このモジュールは以下の文言からドキュメントが開始されている。

This module provides a framework for generating v3 UUIDs

しかし、期待に反して、実際にはほとんどのユーザーは v1 UUID を生成している。 Data::UUID->new->create_str を利用するのが一般的なケースであるためだ。

UUIDv1 の構造について

timestamp 60bit, clock sequence 16bit, node 48bit

となっている。

  UUID                   = time-low "-" time-mid "-"
                           time-high-and-version "-"
                           clock-seq-and-reserved
                           clock-seq-low "-" node

↓つまり以下のような形式となる。

   TIMESTAM-PTIM-ESTA-CLOC-NODEID

/tmp/.UUID_NODEID について

Data::UUID->new で nodeid は生成されます。

/tmp/.UUID_NODEID が存在しない場合、nodeid は md5(gettimeofday(), gethostname()) で生成され、 /tmp/.UUID_NODEID に保存されます。 /tmp/.UUID_NODEID が存在する場合、slurp(/tmp/.UUID_NODEID) + $$ の結果が nodeid となります。

このロジックにより、二回目以後の nodeid 生成は process ごとに独立した nodeid になります。

/tmp/.UUID_STATE について

/tmp/.UUID_STATE に clockseq などが保存されます。これにより、プロセスが再起動した場合などにも動作を続けることができる、という設計に見えます。

このファイルの保存タイミングは以下。

  • UUID 生成時に、前回保存時刻より 10秒おきに保存されます。
  • Data::UUID#DESTROY のコール時にも保存されます。

しかし、現実的には、あまり意味ないかも。

Data::UUID を web application で利用した場合に発生する可能性がある問題について

以下では pre-fork モデルのウェブアプリケーションで Data::UUID を利用した場合に発生し得る問題について議論する。 (Starlet, Starman などの web server が pre-forking モデルです)

時刻の巻戻り

UUID v1 の特性上、NTP などにより時刻が巻き戻った場合、重複した uuid が発行される可能性があります。 時刻の巻き戻りが発生した場合に発生する重複が許容出来ない場合、Data::UUID の利用は避けるべきでしょう。

clock sequence を初期化するタイミングで初期値を random にするなどの軽減策は取られているものの、問題が発生する可能性はそこそこあります。

local 攻撃への脆弱性

  • /tmp/.UUID_NODEID
  • /tmp/.UUID_STATE

の2つのファイルが生成されるが、tmp directory に生成されるために、悪意のあるユーザーが設置することが可能。

nodeid の生成ロジックの問題

/tmp/.UUID_NODEID がある場合にだけ pid が nodeid に考慮される。 このため、Data::UUID を一度も利用したことがない新しいサーバーの初回起動時などに nodeid が同一の Data::UUID インスタンスを持つ process が複数発生する可能性がある。

AWS などで頻繁にインスタンスの作り直しを実施している場合、この問題が発生しやすいと考えられる。

Data::UUID->new() を fork 前に呼んではいけない

Data::UUID->new の中で nodeid を初期化しているので、fork 前に呼ぶと同一 nodeid ですべての子プロセスで利用されることになり、相手は死ぬ。

srand の呼び出し

Data::UUID->new の中で、現在時刻をシードに srand() を呼び出し、それ以後は rand() をコールしている。。 現在時刻は srand のソースとして使うにはよろしくないので、これは、他の rand() を利用している XS module に悪影響を与えている可能性がある。 (Perl 本体は drand48() を利用しているので問題ない)

対案

完全に random な UUID 生成方法である UUID v4 を利用しとくか、sfujiwara さんが作ってたみたいなやつ使うのがいいんじゃないすかね。 ただし、UUID v4 の生成系を利用する場合、fork 時の seed 最初期化などについては考慮を忘れずに。

Created: 2017-11-08 04:34:44 +0000
Updated: 2017-11-08 04:34:44 +0000

spring-boot で tomcat の設定するときのやり方

server:
  use-forwarded-headers: true
  tomcat:
    accesslog:
      directory: /PATH/TO/logs/tomcat/
      pattern: common
      enabled: true
      rotate: true
      # It's required when using remote-ip-header.
      request-attributes-enabled: true
    remote-ip-header: x-forwarded-for
    protocol-header: x-forwarded-proto

なんかこんな感じ? request-attributes-enabled を設定するの忘れると RemoteIpValve で設定した値が AccessLogValve で拾われないので死ぬ。

Created: 2017-11-02 08:11:47 +0000
Updated: 2017-11-02 08:11:47 +0000
Next page