tokuhirom's blog

Re: Java 7 時代の String#split() 事情

http://blog.k11i.biz/2013/05/java-7-stringsplit.html http://d.hatena.ne.jp/chiheisen/20110801/1312119289

Java 7 以後では String#split(String) で、引数が1文字の場合は最適化が効いて、高速になるという話。 社内でなんか話題になってたので最近の JVM だとどうなのかなーと調べてみた(元エントリは5年前のもの)。 (Pattern#split にもいずれ同様の最適化が入る可能性はあるので、このベンチマークの傾向は今でも一緒なのかなーと気になったため)

https://travis-ci.org/tokuhirom/java-string-splitting-benchmark

元のコードは手でベンチマークコードを実装されたものだったが、最近では JMH で簡単に書けるので、JMH で簡単に書いてみた。 以下は travis-ci で openjdk11 を走らせた結果。相変わらず pattern 1 文字の場合の最適化は有効になっていることはわかる。

Benchmark                                           (raw)   Mode  Cnt        Score         Error  Units
StringSplittingBenchmark.patternSplit                      thrpt    5  1550191.136 ±  475915.190  ops/s
StringSplittingBenchmark.patternSplit                  \s  thrpt    5   986040.695 ±  217942.160  ops/s
StringSplittingBenchmark.patternSplit     [ \t\n\x0B\f\r]  thrpt    5  1491352.737 ± 1025015.919  ops/s
StringSplittingBenchmark.patternSplit   |\t|\n|\x0B|\f|\r  thrpt    5   931442.162 ±  108993.004  ops/s
StringSplittingBenchmark.stringSplit                       thrpt    5  1820229.132 ±  564445.853  ops/s
StringSplittingBenchmark.stringSplit                   \s  thrpt    5   817729.421 ±   71545.171  ops/s
StringSplittingBenchmark.stringSplit      [ \t\n\x0B\f\r]  thrpt    5   774425.988 ±  128507.763  ops/s
StringSplittingBenchmark.stringSplit    |\t|\n|\x0B|\f|\r  thrpt    5   384563.020 ±   81302.831  ops/s

実際に travis-ci でバーっと走らせたときの結果は travis のサイトで見ることができる。 (travis 側の負荷状況によって結果が変わるので、参考程度だけれど) https://travis-ci.org/tokuhirom/java-string-splitting-benchmark


ちなみに今回、初めて JMH で @Param などのアノテーションを利用した。

以下のように記述することができて超便利〜。複数のパラメータを利用したベンチマークの実装が驚くほど簡単にできるのだ〜

    @State(Scope.Benchmark)
    public static class BenchmarkState {
        @Param({" ", "\\s", "[ \\t\\n\\x0B\\f\\r]", " |\\t|\\n|\\x0B|\\f|\\r"})
        private String raw;
        private Pattern pattern;

        @Setup
        public void setup() {
            pattern = Pattern.compile(raw);
        }
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    @Benchmark
    public static void patternSplit(BenchmarkState state) {
        state.pattern.split(text);
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    @Benchmark
    public static void stringSplit(BenchmarkState state) {
        text.split(state.raw);
    }
Created: 2018-07-15 00:01:26 +0900
Updated: 2018-07-15 00:01:26 +0900

spring boot + jib を試す

https://github.com/tokuhirom/jib-docker-spring-boot-examples

https://cloudplatform.googleblog.com/2018/07/introducing-jib-build-java-docker-images-better.html jib を使うと java app を極めて簡単に docker image にすることができるというので試してみた。

tutorial 通りにやったらすんなり動いた。あっけない。 よくできている。

Created: 2018-07-10 23:24:14 +0900
Updated: 2018-07-10 23:24:14 +0900

MySQL の X Protocol/X DevAPI 周りについて調査したのをまとめたののメモ

MySQL 8 以後では X Protocol がサポートされている(5.7 系では部分的なサポートであり、X Protocol を本格的に利用する場合には 8 を利用することが推奨されているようだ) 通信は従来の MySQL Protocol と異なり、Protocol Buffers Based となっていて、各言語のドライバの実装が簡単になっている(protocol buffers がその言語でサポートされていれば、だが) これにより今後 libmysqlclient に依存せずに各言語のドライバが実装されるようになって運用管理が簡単になるんじゃないかと私は考えています。 実際に、mysql-connector-nodejsは X Protocol のみをサポートしていて、libmysqlclient への依存がありません。

また、X Protocol/X DevAPI は async を前提に設計されているため、各言語の Connector ではその言語の特性を生かして CompletableFuture/Promise などを利用して実装されています。

X DevAPI というのものがあって、これは MySQL Shell と MySQL Connectors で実装されている API。どの言語を利用していても統一的に MySQL を扱えるプログラミング言語レベルの API になっている。

X Protocol への接続について

X Protocol は mysql 8 ではデフォルトで有効になっている模様。有効かどうかは show plugins などして mysqlx plugin が有効かどうかを確認すれば良い。 port も 3306 ではなく 33060 なので注意。

Node 実装について

Node の MySQL connector は promise based になっており使いやすい * https://dev.mysql.com/doc/dev/connector-nodejs/8.0/ * https://dev.mysql.com/doc/dev/connector-nodejs/8.0/

NODE_DEBUG=protobuf という環境変数を設定すれば、protobuf の serialization のログが見れて便利。

Java 実装について

Java 実装も割と普通に実行できる。今まで通りの MySQL Connector/J の実装で実行可能。 生で使うなら X DevAPI は JDBC API の 2億倍使いやすい。

package com.example;

import com.mysql.cj.xdevapi.Session;
import com.mysql.cj.xdevapi.SessionFactory;
import com.mysql.cj.xdevapi.SqlResult;

import java.util.List;
import java.util.stream.Collectors;

public class App {
    public static void main(String[] args) {
        SessionFactory sessionFactory = new SessionFactory();
        Session session = sessionFactory.getSession("mysqlx://root@127.0.0.1:33060/test");
        runQuery(session, "SHOW PROCESSLIST");
        runQuery(session, "SELECT SLEEP(15)");
        runQuery(session, "SHOW PROCESSLIST");
        session.close();
    }

    private static void runQuery(Session session, String query) {
        System.out.println("😁 クエリ開始" + query);
        session.sql(query).executeAsync()
                .thenAccept(rows -> {
                    System.out.println("😁 クエリ完了 " + query);
                    dumpRows(rows);
                });
    }

    private static void dumpRows(SqlResult rows) {
        List<String> columnNames = rows.getColumnNames();
        System.out.println("\n\n結果結果結果結果結果結果結果結果結果結果結果");
        System.out.println("    " + columnNames.stream().collect(Collectors.joining("\t")));
        System.out.println(rows.fetchAll()
                .stream()
                .map(row -> columnNames.stream()
                        .map(row::getString)
                        .collect(Collectors.joining("\t")))
                .map(line -> "    " + line)
                .collect(Collectors.joining("\n")));
        System.out.println("\n\n終了終了終了終了終了終了終了終了終了終了終了");
    }
}

のような実装では、以下のような結果を得るだろう。

😁 クエリ開始SHOW PROCESSLIST
😁 クエリ完了 SHOW PROCESSLIST


結果結果結果結果結果結果結果結果結果結果結果
    Id    User    Host    db    Command    Time    State    Info
    4    event_scheduler    localhost    null    Daemon    342510    Waiting on empty queue    null
    25    root    172.17.0.1:36154    null    Sleep    26244        null
    102    root    172.17.0.1:39884    test    Sleep    1599    null    PLUGIN
    204    root    172.17.0.1:40090    test    Query    0    null    PLUGIN: SHOW PROCESSLIST


終了終了終了終了終了終了終了終了終了終了終了
😁 クエリ開始SELECT SLEEP(15)
😁 クエリ開始SHOW PROCESSLIST
😁 クエリ完了 SELECT SLEEP(15)


結果結果結果結果結果結果結果結果結果結果結果
    SLEEP(15)
    0


終了終了終了終了終了終了終了終了終了終了終了
😁 クエリ完了 SHOW PROCESSLIST


結果結果結果結果結果結果結果結果結果結果結果
    Id    User    Host    db    Command    Time    State    Info
    4    event_scheduler    localhost    null    Daemon    342525    Waiting on empty queue    null
    25    root    172.17.0.1:36154    null    Sleep    26259        null
    102    root    172.17.0.1:39884    test    Sleep    1614    null    PLUGIN
    204    root    172.17.0.1:40090    test    Query    0    null    PLUGIN: SHOW PROCESSLIST


終了終了終了終了終了終了終了終了終了終了終了

com.mysql.cj.protocol.x.AsyncMessageSender#writeAsync"[SEND] ===> " + message.getMessage().getClass().getSimpleName() + "\n" + message.getMessage().toString()というデバッグログを設置して、com.mysql.cj.protocol.x.ResultMessageListener#createFromMessage"[RECEIVE] <== " + message.getMessage().getClass().getName() + "\n" + message.getMessage().toString() というデバッグログを設置すると、通信の様子を垣間見ることができる。

上記のコードの場合の出力は以下のようになる。

[SEND] ===> CapabilitiesGet

[SEND] ===> CapabilitiesSet
capabilities {
  capabilities {
    name: "tls"
    value {
      type: SCALAR
      scalar {
        type: V_BOOL
        v_bool: true
      }
    }
  }
}

[SEND] ===> AuthenticateStart
mech_name: "PLAIN"
auth_data: "test\000root\000"

[SEND] ===> StmtExecute
stmt: "select @@mysqlx_max_allowed_packet"

😁 クエリ開始SHOW PROCESSLIST
[SEND] ===> StmtExecute
stmt: "SHOW PROCESSLIST"

😁 クエリ開始SELECT SLEEP(15)
[SEND] ===> StmtExecute
stmt: "SELECT SLEEP(15)"

😁 クエリ開始SHOW PROCESSLIST
[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "Id"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 21
flags: 16

[SEND] ===> StmtExecute
stmt: "SHOW PROCESSLIST"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "User"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 96
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Host"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 192
flags: 16

[SEND] ===> Close

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "db"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 192

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Command"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 48
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "Time"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 7
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "State"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 90

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Info"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 300

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\b"
field: "event_scheduler\000"
field: "localhost\000"
field: ""
field: "Daemon\000"
field: "\216\353)"
field: "Waiting on empty queue\000"
field: ""

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "2"
field: "root\000"
field: "172.17.0.1:36154\000"
field: ""
field: "Sleep\000"
field: "\272\235\003"
field: "\000"
field: ""

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\314\001"
field: "root\000"
field: "172.17.0.1:39884\000"
field: "test\000"
field: "Sleep\000"
field: "\260\034"
field: ""
field: "PLUGIN\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\234\003"
field: "root\000"
field: "172.17.0.1:40094\000"
field: "test\000"
field: "Query\000"
field: "\000"
field: ""
field: "PLUGIN: SHOW PROCESSLIST\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$FetchDone

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxNotice$Frame
type: 3
scope: LOCAL
payload: "\b\004\022\004\b\002\030\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxSql$StmtExecuteOk

😁 クエリ完了 SHOW PROCESSLIST


結果結果結果結果結果結果結果結果結果結果結果
    Id    User    Host    db    Command    Time    State    Info
    4    event_scheduler    localhost    null    Daemon    342727    Waiting on empty queue    null
    25    root    172.17.0.1:36154    null    Sleep    26461        null
    102    root    172.17.0.1:39884    test    Sleep    1816    null    PLUGIN
    206    root    172.17.0.1:40094    test    Query    0    null    PLUGIN: SHOW PROCESSLIST


終了終了終了終了終了終了終了終了終了終了終了
[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "SLEEP(15)"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 21
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$FetchDone

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxNotice$Frame
type: 3
scope: LOCAL
payload: "\b\004\022\004\b\002\030\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxSql$StmtExecuteOk

😁 クエリ完了 SELECT SLEEP(15)


結果結果結果結果結果結果結果結果結果結果結果
    SLEEP(15)
    0


終了終了終了終了終了終了終了終了終了終了終了
[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "Id"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 21
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "User"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 96
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Host"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 192
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "db"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 192

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Command"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 48
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "Time"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 7
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "State"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 90

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Info"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 300

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\b"
field: "event_scheduler\000"
field: "localhost\000"
field: ""
field: "Daemon\000"
field: "\254\353)"
field: "Waiting on empty queue\000"
field: ""

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "2"
field: "root\000"
field: "172.17.0.1:36154\000"
field: ""
field: "Sleep\000"
field: "\330\235\003"
field: "\000"
field: ""

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\314\001"
field: "root\000"
field: "172.17.0.1:39884\000"
field: "test\000"
field: "Sleep\000"
field: "\316\034"
field: ""
field: "PLUGIN\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\234\003"
field: "root\000"
field: "172.17.0.1:40094\000"
field: "test\000"
field: "Query\000"
field: "\000"
field: ""
field: "PLUGIN: SHOW PROCESSLIST\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$FetchDone

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxNotice$Frame
type: 3
scope: LOCAL
payload: "\b\004\022\004\b\002\030\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxSql$StmtExecuteOk

😁 クエリ完了 SHOW PROCESSLIST


結果結果結果結果結果結果結果結果結果結果結果
    Id    User    Host    db    Command    Time    State    Info
    4    event_scheduler    localhost    null    Daemon    342742    Waiting on empty queue    null
    25    root    172.17.0.1:36154    null    Sleep    26476        null
    102    root    172.17.0.1:39884    test    Sleep    1831    null    PLUGIN
    206    root    172.17.0.1:40094    test    Query    0    null    PLUGIN: SHOW PROCESSLIST


終了終了終了終了終了終了終了終了終了終了終了

現在の Java connector/mysqld の実装では、select sleep(15) などのクエリが発行された場合、その後のクエリの結果が先に帰ってくることはない。これは実際問題、session が状態を持つ以上、そうならざるを得ない。このため、他の状態を持たないプロトコルのクライアントと同じ気分で使っているとハマるかも。 そして、transaction は session に紐づく が、 1session あたり 1 TCP connection 以上 という実装に現時点ではなっている(以上、というのは slave への自動送信などを x devapi 上で 1 セッションとして扱う可能性があるため)。 (通信を多重化することも可能だったと思うが、現在の実装はそうなっていない。なんでだろうか。MySQL Server の実装上の制約?)

Created: 2018-07-08 23:39:44 +0900
Updated: 2018-07-08 23:39:44 +0900

Shibuya.pm で plenv/perl-build について LT した

https://www.slideshare.net/tokuhirom/plenv-and-perl-build-and-searchcpanorg

perl 5.28.0 をインストールする際には perl-build のバージョンアップが必要ですよ、というアナウンス。

Created: 2018-07-08 00:27:52 +0900
Updated: 2018-07-08 00:27:52 +0900

Nginx caches DNS records forever in proxy_redirect directive

If a domain name resolves to several addresses, all of them will be used in a round-robin fashion. In addition, an address can be specified as a server group.

The parameter value can contain variables. In this case, if an address is specified as a domain name, the name is searched among the described server groups, and, if not found, is determined using a resolver. <<<

http://d.hatena.ne.jp/hirose31/20131112/1384251646

Created: 2018-06-27 15:55:50 +0900
Updated: 2018-06-27 15:55:50 +0900

OpenJDK についての思い

  1. Oracle put all JDK source codes on the OpenJDK repository.
  2. Oracle won't provide free JDK distribution in a future release. Oracle will provide only commercial binary distribution. It's built from source code in the OpenJDK repository.
  3. Some providers will build OpenJDK binary distribution from OpenJDK repository. AdoptOpenJDK, Zulu, and other providers will provide free binary distribution.
  4. Both 2. and 3. contains same features.

As a result, we can use free binary distribution.

(Of course, there's an OpenJDK rpm in CentOS' yum repo. You can use it.)

Created: 2018-06-27 11:49:51 +0900
Updated: 2018-06-27 11:49:51 +0900

centos7 で time zone を変更する

timedatectl set-timezone America/Chicago

これでOK

https://www.cyberciti.biz/faq/centos-linux-6-7-changing-timezone-command-line/

Created: 2018-06-26 14:49:25 +0900
Updated: 2018-06-26 14:49:25 +0900

github pages で underscore を含むディレクトリが表示されないとき

具体的には amon.64p.org を github pages に移したのだが、その際にハマった。 sphinx はデフォルトで _static/ 以下に静的ファイルを生成するからだ。

https://help.github.com/articles/files-that-start-with-an-underscore-are-missing/ に解決策が載ってるよ、と tmaesaka さんに教えてもらった。

.nojekyll 置いて解決。

Created: 2018-06-26 14:19:08 +0900
Updated: 2018-06-26 14:19:08 +0900

OWASP dependency-check を利用して脆弱性のある Java ライブラリに依存していないか確認する

依存しているライブラリに脆弱性がある場合、それを検出できると嬉しい。

OWASP dependency-check の gradle プラグインを入れると、簡単に検出が可能となる。

設定は以下のようであり、非常に容易である。

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath('org.owasp:dependency-check-gradle:3.2.1')
    }
}
apply plugin: 'org.owasp.dependencycheck'

check.dependsOn dependencyCheckAnalyze

// https://jeremylong.github.io/DependencyCheck/dependency-check-gradle/configuration.html
dependencyCheck {
    // Threshold value to fail build
    // https://www.first.org/cvss/specification-document
    // 0.1-3.9=low 4.0-6.9=medium 7.0-8.9=High 9.0-10.0=Critical
    failBuildOnCVSS=0
}

failBuildOnCVSS の値を設定することで、CVSS Score が 7.0 以上、つまり脆弱性としての評価が High 以上のもんのの場合は FAIL する、というようなルールを設定することが可能だ。

例えば、spring boot 1.5.0 に依存している場合は以下のような出力になる。

$ ./gradlew check
:dependencyCheckAnalyze
Verifying dependencies for project demo
Checking for updates and analyzing vulnerabilities for dependencies
Generating report for project demo
Found 24 vulnerabilities in project demo


One or more dependencies were identified with known vulnerabilities:

spring-boot-starter-security-1.5.0.RELEASE.jar (org.springframework.boot:spring-boot-starter-security:1.5.0.RELEASE, cpe:/a:pivotal_software:spring_boot:1.5.0, cpe:/a:pivotal_software:spring_security:1.5.0) : CVE-2017-8046, CVE-2018-1196
spring-boot-starter-1.5.0.RELEASE.jar (cpe:/a:pivotal_software:spring_boot:1.5.0, org.springframework.boot:spring-boot-starter:1.5.0.RELEASE) : CVE-2017-8046, CVE-2018-1196
spring-aop-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, org.springframework:spring-aop:4.3.6.RELEASE, cpe:/a:pivotal:spring_framework:4.3.6) : CVE-2018-1199
spring-security-config-4.2.1.RELEASE.jar (org.springframework.security:spring-security-config:4.2.1.RELEASE, cpe:/a:pivotal_software:spring_security:4.2.1) : CVE-2017-4995, CVE-2018-1199
spring-security-web-4.2.1.RELEASE.jar (org.springframework.security:spring-security-web:4.2.1.RELEASE, cpe:/a:pivotal_software:spring_security:4.2.1) : CVE-2017-4995, CVE-2018-1199
spring-boot-1.5.0.RELEASE.jar (cpe:/a:pivotal_software:spring_boot:1.5.0, org.springframework.boot:spring-boot:1.5.0.RELEASE) : CVE-2017-8046, CVE-2018-1196
spring-boot-autoconfigure-1.5.0.RELEASE.jar (cpe:/a:pivotal_software:spring_boot:1.5.0, org.springframework.boot:spring-boot-autoconfigure:1.5.0.RELEASE) : CVE-2017-8046, CVE-2018-1196
spring-boot-starter-logging-1.5.0.RELEASE.jar (cpe:/a:pivotal_software:spring_boot:1.5.0, org.springframework.boot:spring-boot-starter-logging:1.5.0.RELEASE) : CVE-2017-8046, CVE-2018-1196
spring-core-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, org.springframework:spring-core:4.3.6.RELEASE, cpe:/a:pivotal:spring_framework:4.3.6) : CVE-2018-1199
spring-beans-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, org.springframework:spring-beans:4.3.6.RELEASE, cpe:/a:pivotal:spring_framework:4.3.6) : CVE-2018-1199
spring-security-core-4.2.1.RELEASE.jar (org.springframework.security:spring-security-core:4.2.1.RELEASE, cpe:/a:pivotal_software:spring_security:4.2.1) : CVE-2017-4995, CVE-2018-1199
spring-context-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, cpe:/a:pivotal:spring_framework:4.3.6, org.springframework:spring-context:4.3.6.RELEASE) : CVE-2018-1199
spring-expression-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, cpe:/a:pivotal:spring_framework:4.3.6, org.springframework:spring-expression:4.3.6.RELEASE) : CVE-2018-1199
spring-web-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, cpe:/a:pivotal:spring_framework:4.3.6, org.springframework:spring-web:4.3.6.RELEASE) : CVE-2018-1199
logback-classic-1.1.9.jar (cpe:/a:logback:logback:1.1.9, ch.qos.logback:logback-classic:1.1.9) : CVE-2017-5929
logback-core-1.1.9.jar (cpe:/a:logback:logback:1.1.9, ch.qos.logback:logback-core:1.1.9) : CVE-2017-5929


See the dependency-check report for more details.


:dependencyCheckAnalyze FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':dependencyCheckAnalyze'.
>

  Dependency-Analyze Failure:
  One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '0.0': CVE-2017-5929, CVE-2017-8046, CVE-2017-4995, CVE-2018-1199, CVE-2018-1196
  See the dependency-check report for more details.



* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 6.561 secs

report は HTML で出力される(JSON, CSV 等の形式も指定可能)。 実際のレポートはこのような感じになる → https://jeremylong.github.io/DependencyCheck/general/SampleReport.html

ant/maven のタスクも用意されている。

Created: 2018-06-08 14:28:47 +0900
Updated: 2018-06-08 14:28:47 +0900

このブログについて

まあなんか、そんなにブログに書くこともないというか、うーん。 このブログ、Ruby で書かれているんですが、Ruby で書かれていると、変更しづらくて。。

いずれ Ruby 以外の言語で書き直したいと思ってて時々ちょいちょい書き直してるけど、リリースまでは至らずに消してる。 そんな日々です。

Created: 2018-05-30 15:28:31 +0900
Updated: 2018-05-30 15:28:31 +0900

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 11:20:02 +0900
Updated: 2018-05-23 11:20:02 +0900

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 11:10:21 +0900
Updated: 2018-05-14 11:10:21 +0900

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 10:59:37 +0900
Updated: 2018-04-10 10:59:37 +0900

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 11:47:07 +0900
Updated: 2018-03-27 11:47:07 +0900

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 13:19:31 +0900
Updated: 2018-03-06 13:19:31 +0900

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

Created: 2018-03-05 13:57:06 +0900
Updated: 2018-03-05 13:57:06 +0900

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 19:32:41 +0900
Updated: 2018-03-01 19:32:41 +0900

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 23:13:43 +0900
Updated: 2018-02-25 23:13:43 +0900

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 19:16:43 +0900
Updated: 2018-01-31 19:16:43 +0900

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 23:19:48 +0900
Updated: 2018-01-05 23:19:48 +0900

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 23:17:14 +0900
Updated: 2017-12-23 23:17:14 +0900
Next page