Blog

ContainerCreating で k8s が止まってるとき

https://serverfault.com/questions/728727/kubernetes-stuck-on-containercreating

kubectl describe pods したら原因がわかるようだ。

k8s の設定とアプリケーションコードのレポジトリは分けるべきか?

CI のサイクルが違うので、分けておいたほうが開発しやすいようだ。 アプリケーションコードをいじるたびにベースイメージのビルドをしていると時間がかかる。

JFR の event stream を Fluency で fluentd に送る

ここまでできれば、あとは fluentd で storage に格納して、flamegraph 等を描画すれば良いだけである。

import jdk.jfr.consumer.RecordedEvent
import jdk.jfr.consumer.RecordingStream
import org.komamitsu.fluency.Fluency
import org.slf4j.LoggerFactory
import java.time.Duration
import java.time.ZoneId

class JfmonClient(
    private val fluency: Fluency,
    private val tag: String,
    private val siteId: String,
    private val instanceId: String
) : AutoCloseable {
    private var rs: RecordingStream = RecordingStream()
    private val logger = LoggerFactory.getLogger(JfmonClient::class.java)

    fun start() {
        this.rs.start()
    }

    fun startAsync() {
        this.rs.startAsync()
    }

    override fun close() {
        logger.info("Closing jfmon-client")
        this.rs.close()

        this.fluency.flush()
        this.fluency.close()
    }

    fun flush() {
        logger.info("Flushing jfmon-client")
        this.fluency.flush()
    }

    /**
     * jdk.SocketRead: [jdk.jfr.events.SocketReadEvent]
     */
    fun enable(name: String, period: Duration?, threshold: Duration? = null, stackTrace: Boolean? = null) {
        logger.info("Enabling {}(period={}, threshold={}, stackTrace={})", name, period, threshold, stackTrace)

        val settings = rs.enable(name)
        if (period != null) {
            settings.withPeriod(period)
        }
        if (threshold != null) {
            settings.withThreshold(threshold)
        }
        if (stackTrace != null) {
            if (stackTrace) {
                settings.withStackTrace()
            } else {
                settings.withoutStackTrace()
            }
        }
        rs.onEvent(name, this::emitEvent)
    }

    private fun emitEvent(event: RecordedEvent) {
        val data = buildMap(event)
        if (logger.isDebugEnabled) {
            logger.debug(
                "{}: {} {}",
                event.eventType.name,
                event.startTime.atZone(ZoneId.of("Asia/Tokyo")),
                data
            )
        }
        fluency.emit(tag, data)
    }

    private fun buildMap(event: RecordedEvent): Map<String, Any?> {
        return mapOf(
            "siteId" to siteId,
            "instanceId" to instanceId,
            "type" to event.eventType.name,
            "data" to buildData(event)
        )
    }

    private fun buildData(event: RecordedEvent): Map<String, Any?> {
        return event.fields.associate { dv ->
            val value: Any? = when (dv.typeName) {
                "boolean", "long", "int", "java.lang.String" -> event.getValue<Any>(dv.name)
                "java.lang.Thread" -> event.getThread(dv.name).javaName
                "jdk.types.StackTrace" -> {
                    val stackTrace = event.stackTrace
                    stackTrace?.frames?.map { frame ->
                        // 型が違うものを一つの配列に入れると、Elasticsearch が怒る。
                        listOf(
                            frame.method?.type?.name,
                            frame.method?.name?.toString(),
                            frame.lineNumber.toString(),
                        )
                    }
                }
                else -> null
            }
            dv.name to value
        }
    }
}

JFR のスタックトレースはデフォルトで64

JFR では Stacktrace を取得する処理があるが、デフォルトのスタック取得深度は 64 である。 現代のウェブアプリケーションではフレームワーク側で64スタックぐらい余裕で消費しているので、これは増加させる必要がある。あまり大きく設定すると、メモリを圧迫する可能性があるので、できるだけ小さく、かつ各アプリケーションで必要なぶんなスタックの深さを指定するのが良い。

datadog の agent では、256 を設定させている。 https://docs.datadoghq.com/ja/tracing/profiler/enabling/java/

JFR の設定ファイルの中で、stackdepth を指定できる方法がないかと調べたが見当たらない。

2016 年の報告では、G1GCのときに stachdepth を深くしたら遅くなったという報告があるが、今だったらカイゼンされてたりしないのかなぁ。と思ったりする。 https://blogs.oracle.com/poonam/g1-and-flight-recorders-xx:flightrecorderoptionsstackdepth-option

JFR の jdk.ExecutionSample では、STATE_RUNNABLE な Thread しか見れない。

https://stackoverflow.com/questions/60156403/why-does-the-hotspot-executionsample-event-always-return-state-runnable

必要であれば、async-profiler とかで見たほうが良いよ、と。 逆にいうと、STATE_RUNNABLE だけをとってるから、待ちになりがちな web system の場合、容量少なくプロファイリングできるということだな。

jfr で取れる生データがどんなものか確認できるウェブアプリを作った

https://github.com/tokuhirom/jfrdemo

JFR のイベントってどんなんあるんかなーと思ってもドキュメントとかが見当たらないし、確認の方法がよくわからないわけだが、、 実データを見るのが一番良さそうなのだが、実データ見るのがめんどくさいので、ある程度データを見れるように整理したというわけ。

Tomcat の session id の生成がめちゃくちゃ遅いとき

Sep 27 23:41:00 dev3 docker[56810]: 2021-09-27 23:41:00.043  WARN 1 --- [nio-8080-exec-1] o.a.c.util.SessionIdGeneratorBase        : Creation
 of SecureRandom instance for session ID generation using [SHA1PRNG] took [140,998] milliseconds.

というようなエラーが起きるときがある(VPS などで /dev/random の entropy が十分ではない場合)。

https://newbedev.com/what-exactly-does-djava-security-egd-file-dev-urandom-do-when-containerizing-a-spring-boot-application

これは FAQ で、 Java の起動オプションに -Djava.security.egd=file:/dev/./urandom とつければよい。

spring boot のデフォルトは /dev/urandom を読む実装でいいと思うけれど。

ブログを MySQL ベースにした

昔はブログを MySQL ベースで運用していたのだが、、MySQL ベースでやってると運用がちょっとめんどくさくて、お金をかけずに自前でブログを運用するとなると、ファイルベースで管理したほうがなにかと楽だったというような歴史的経緯があり、ファイルベースでやっていたのだが、Digital Ocean の managed mysql が思ったより使い勝手が良いので、これを使うことにした。$15/Month ぐらい。 MySQL ベースのほうが、プログラミング言語を変える、とかもやりやすい。気がする(今のブログシステムは、なぜか最近全然使ってない Ruby で書かれていて、メンテがちょっとしんどい)。

VPS を Ubuntu に変えたよ

何かの作業するとき用に VPS をなんとなく契約しているのだけど、すごい昔の centos で色々めんどくさくなったのでふっとばして ubuntu 20.04 に変更したよ。

gradle の test 失敗情報を stdout に出させる。

    test {
        useJUnitPlatform()
        testLogging {
            // Make sure output from standard out or error is shown in Gradle output.
            showStandardStreams true
            showExceptions true
            showCauses true
            showStackTraces true
            exceptionFormat TestExceptionFormat.FULL
        }
    }

とかする。github actions とかの場合は設定しておくと便利。

PowerShell の New-ScheduledTaskTrigger で、DaysOfWeek に複数要素指定する

New-ScheduledTaskTrigger -Weekly -At $At -DaysOfWeek @("Monday", "Tuesday", "Wednesday", "Thursday", "Friday")

PowerShell 力が低くて難しかったけど、これで動いてそう。

Windows 10 で caps2ctrl が動かないときは powertoys 使う

caps2ctrl で remap していたが、なんか動かなくてぐぐってみると、microsoft が出している powertoys なら動くとのこと。 実際 powertoys を使ったらちゃんと動いた。 https://github.com/microsoft/PowerToys

lettuce で redis cluster を使ってる場合に、プロセスがうまく落ちないってとき

lettuce-timer のスレッドとかが残ってうまくプロセスがシャットダウンでき無いとき。

Lettuce の場合 StatefulRedisClusterConnection などのコネクションオブジェクトと RedisClusterClient の他に ClientResources も shutdown しないと、うまくプロセスが終わらないので要注意。

見逃しがち。

https://github.com/tokuhirom/lettuce-exit-normally/

Wikipedia から SKK の辞書を生成するスクリプトをかいた。

https://github.com/tokuhirom/jawiki-kana-kanji-dict

SKK-JISYO.L が最近、メンテナンスされてなくて れいわ /令和/ が入ってなかったりして困ってしまう。

そこで、neologd から辞書を生成してみた。 https://github.com/tokuhirom/skk-jisyo-neologd/ しかし、neologd は、形態素解析用の辞書としてはいいと思うのですが、かな漢字変換用として無理矢理使おうとすると誤変換になってしまうケースが多かった。

なので、直接 wikipedia からデータを抽出することにしてみた python で適当に抽出するスクリプトを書いて、github actions で設定した。これで、何もしなくても自動的に辞書がアップデートされていくはず。

工夫したこととかのメモ

pytest でテスト名称がマルチバイト文字のときにエスケープされて見辛いとき

pytest.ini に以下のように記述すればよい。

[pytest]

disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true

https://github.com/pytest-dev/pytest/pull/4995

Mac で synergy がうごかないとき

Linux を server, Mac を client として synergy を使おうとした。

[2019-03-13T22:16:56] DEBUG: can't get the active group, use the first group instead

とかなんとか言われて、うまく動かない場合。

MacOS 側に "U.S." キーボードレイアウトがないと日本語入力できないらしい!これはわからん!!

Settings の "Keyboard" -> "Input Sources" から U.S. を追加すればOK。Google IME だけ、とかにしてると使えないのだった。

hirose31 san におしえてもらって解決しました!!!

ref. https://members.symless.com/forums/topic/6176-keyboard-does-not-work-clientmacos-mojave-serverubuntu-1804/

mecab-ipadic-neologd-git の AUR package をなおした

なんかよくわからんが fakeroot の下だと file コマンドがうごかないのでなぞだった。

https://aur.archlinux.org/packages/mecab-ipadic-neologd-git/

なんかそれっぽく直した。

SparkSQL のクエリをユニットテストしたい

品質向上のために Spark クエリのユニットテストを実施したいという場合、JVM 言語で開発している場合には、Spark/hive をライブラリとしてロードできるから、容易に実装することができる。

dependencies {
    implementation 'org.apache.spark:spark-core_2.12:3.0.0'
    implementation 'org.apache.spark:spark-sql_2.12:3.0.0'
}

のように、関連するモジュールを依存に追加する。

以下のような、テストに利用するデータを json 形式などで用意する(spark は CSV, TSV などの形式も利用可能だから、好きなものを使えばよい)

{"name": "Nick",	"age":35,	"extra_fields": "{\"interests\":[\"car\", \"golf\"]}"}
{"name": "John",	"age":23}
{"name":"Cathy",	"age":44,	"extra_fields":"{\"interests\":[\"cooking\"]}"}

あとは、実際に spark session を作成し、local モードで spark を起動させれば良い。

import org.apache.spark.sql.Dataset
import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession

// test without
class SimpleTest {
    fun run() {
        val spark: SparkSession = SparkSession
            .builder()
            .appName("Java Spark SQL basic example") // your application name
            .config("spark.master", "local")  // run on local machine, single thread.
            .config("spark.ui.enabled", false)
            .getOrCreate()

        val resourcePath = javaClass.classLoader.getResource("test-data/people.json")!!.toString()
        println("++++ read csv from: $resourcePath")

        val df = spark.read()
            .json(resourcePath)
        df.show()
        df.printSchema()

        println("++++ create table")
        df.createTempView("people")

        println("++++ select")
        val sqlDF: Dataset<Row> = spark.sql("SELECT * FROM people")
        sqlDF.show(false)

        println("++++ select 2nd")
        val sqlDF2: Dataset<Row> = spark.sql("SELECT name, get_json_object(extra_fields, '$.interests') interests FROM people")
        sqlDF2.show()

        println("++++ select 3rd")
        val sqlDF3: Dataset<Row> = spark.sql("SELECT avg(age) avg_age FROM people")
        sqlDF3.show()
    }
}

fun main() {
    SimpleTest().run()
}

hive を使う場合

実際に動かすクエリが select * from your_db_name.your_table のようにDB 名を指定していて、そのクエリ自体を変えずにテストしたいという場合には、hive サポートを有効にする必要がある。

hive を使う場合、spark-hive を依存に追加する。

dependencies {
    implementation 'org.apache.spark:spark-core_2.12:3.0.0'
    implementation 'org.apache.spark:spark-sql_2.12:3.0.0'
    implementation 'org.apache.spark:spark-hive_2.12:3.0.0'
}

あとは以下のように DB を作って入れるだけ。

import org.apache.spark.sql.Dataset
import org.apache.spark.sql.Row
import org.apache.spark.sql.SaveMode
import org.apache.spark.sql.SparkSession

class TestClass {
    fun run() {
        val warehouseLocation = createTempDir()
        println("++++ warehouseLocation=$warehouseLocation")
        val spark: SparkSession = SparkSession
            .builder()
            .appName("Java Spark SQL basic example") // your application name
            .config("spark.master", "local")  // run on local machine, single thread.
            .config("spark.sql.warehouse.dir", warehouseLocation.toString())
            .config("spark.ui.enabled", false)
            .enableHiveSupport()
            .getOrCreate()

        val resourcePath = javaClass.classLoader.getResource("test-data/people.json")!!.toString()
        println("++++ read csv from: $resourcePath")

        val df = spark.read()
            .json(resourcePath)
        df.show()
        df.printSchema()

        println("++++ create table")
        spark.sql("create database if not exists foo")
        df.write().mode(SaveMode.Overwrite).saveAsTable("foo.people")
        spark.sql("show tables").show()
        spark.sql("show create table foo.people").show(false)

        // If the type of data is the important thing, you need to write your schema by yourself.
        //        spark.sql("""drop table if exists `foo`.`people`""")
//        spark.sql("""
//        CREATE TABLE `foo`.`people` (
//            `name` STRING,
//            `age` long,
//            `extra_fields` STRING)
//        USING parquet""".trimIndent())
//        df.write().insertInto("foo.people")


        println("++++ select")
        val sqlDF: Dataset<Row> = spark.sql("SELECT * FROM foo.people")
        sqlDF.show(false)

        println("++++ select 2nd")
        val sqlDF2: Dataset<Row> = spark.sql("SELECT name, get_json_object(extra_fields, '$.interests') interests FROM foo.people")
        sqlDF2.show()

        println("++++ select 3rd")
        val sqlDF3: Dataset<Row> = spark.sql("SELECT avg(age) avg_age FROM foo.people")
        sqlDF3.show()
    }
}

fun main() {
    TestClass().run()
}

クエリを変更しなくていいというメリットがある一方で、hive にアクセスするので依存も増えるし、実行もめちゃくちゃ遅くなります。

df.write().mode(SaveMode.Overwrite).saveAsTable("foo.people")

のようにすると、df 側の型をみていい感じにテーブル定義してくれて便利だが、明示的に create table したいときは以下のようにしたほうがいいかも。

        spark.sql("""drop table if exists `foo`.`people`""")
        spark.sql("""
        CREATE TABLE `foo`.`people` (
            `name` STRING,
            `age` long,
            `extra_fields` STRING)
        USING parquet""".trimIndent())
        df.write().insertInto("foo.people")

両者の比較

hive を利用しない場合、上記コードは 4.427 sec 程度で終わりますが、hive を利用する場合は 19.676 sec 程度かかるようになります。 プロダクションコードのテストをする場合はこの差はそこそこでかいかも。

sample code

https://github.com/tokuhirom/sparksql-unittest

curl で silence したいけどエラーはみたい。

       -s, --silent
              Silent or quiet mode. Don't show progress meter or error messages.  Makes Curl mute.

で silence できるが、これを入れると、error message も抑制されてしまう。

       -S, --show-error
              When used with -s it makes curl show an error message if it fails.

-S を追加で入れると、エラーは stderr に出るようになるのでちょうどいい感じになる。

SystemRules ではなく SystemLambda を使う

https://github.com/stefanbirkner/system-rules

stdout/stderr の出力をキャプチャするのに systemrules が便利だが、junit5 対応はしていない。 junit5 に対応するためには system-labmda を使う。

https://github.com/stefanbirkner/system-lambda

インターフェースも junit に密結合していなくて、きれい。

適当に JDBC でデータとってきてダンプするスニペット

生JDBCで適当にデータ出してデバッグしたいって時につかうやつです。

    protected void selectAndPrint(Connection connection, String query) {
        log.info("======> dumpTable: {} <====", query);
        try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
            try (ResultSet rs = preparedStatement.executeQuery()) {
                ResultSetMetaData metaData = rs.getMetaData();
                log.info("| {} |", IntStream.range(0, metaData.getColumnCount())
                                            .mapToObj(i -> {
                                                try {
                                                    return metaData.getColumnName(i + 1);
                                                } catch (SQLException e) {
                                                    throw new RuntimeException(e);
                                                }
                                            }).collect(Collectors.joining(" | ")));
                while (rs.next()) {
                    log.info("| {} |", IntStream.range(0, metaData.getColumnCount())
                                                .mapToObj(i -> {
                                                    try {
                                                        return rs.getString(i + 1);
                                                    } catch (SQLException e) {
                                                        throw new RuntimeException(e);
                                                    }
                                                }).collect(Collectors.joining(" | ")));
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

Swift で Foundation の data をバイト列で初期化したい

Data([0xDE, 0xAD, 0xBE, 0xEF]) こんな感じ。Data とか、一般的な名前すぎてググってもなんか見つけにくい。

thrift compiler のバイナリを static build したい

http://archive.apache.org/dist/thrift/ からダウンロードする。

https://stackoverflow.com/questions/20068947/how-to-static-link-linux-software-that-uses-configure

linux の場合

sudo yum install glibc-static -y
./configure --enable-static --without-ruby --without-nodejs --without-php --without-python --without-c_glib --without-go --without-nodejs --without-lua CFLAGS="-static"
make -j9 LDFLAGS="-all-static"

osx の場合

osx では static build ができないので諦める。

brew install bison
export PATH="/usr/local/opt/bison/bin:$PATH"
export LDFLAGS="-L/usr/local/opt/bison/lib"
./configure --without-ruby --without-nodejs --without-php --without-python --without-c_glib --without-go --without-nodejs --without-lua
make -j 9

[C#] C# で byte 列を16進数に変換したい

https://stackoverflow.com/questions/623104/byte-to-hex-string

の通りにやればいい。

byte[] data = { 1, 2, 4, 8, 16, 32 };
string hex = BitConverter.ToString(data);

// Result: 01-02-04-08-10-20

標準ライブラリでこういう表現できるのは便利だなーという感想。

Gradle の dependency locking について

昔の gradle には dependency locking 機能がなかった。ビルドするタイミングによって、別の依存モジュールが利用されたりしていた。。 最近、gradle に dependency locking 機能がついたので試してみた。 carton.lock とか package-lock.json とか、そういうのと同じようなことができるようになる。 同じレポジトリからビルドしたら同じ jar が生成されるようになる。便利。

dependency locking を利用すると gradle.lockfile というファイルが生成される。

デフォルトだとフェーズ単位でファイルが生成されるから enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') を settings.gradle に書いて1ファイルにまとめるようにしたほうが良い。gradle 7.0 ではこの方式がデフォルトになる予定なので、最初からこの feature flag は enabled にしたほうが良いです。管理上も、そのほうが便利。

たぶんもう普通に使えるけど、まだ開発途中って感じはする。./gradlew dependencies してもサブプロジェクトのぶんを一括で作れない、とか。。

↓実際に line-bot-sdk-java を利用して試しに生成してみたやつがこれ。 https://github.com/tokuhirom/line-bot-sdk-java/commit/08a53ed86eedcf1072e7c12e77d7e1777f54c933

PowerShell に git branch 情報を表示する

https://github.com/dahlbyk/posh-git

を利用すればいい。https://www.powershellgallery.com/packages/posh-git/1.0.0-beta4 PowerShell gallery からインストールすればいいです。

profile.ps1 に以下のように設定した。

Import-Module posh-git

function prompt {
    $prompt = & $GitPromptScriptBlock
    if ($prompt) { "$prompt " } else { " " }
}

$global:GitPromptSettings.DefaultPromptAbbreviateHomeDirectory = $true
$global:GitPromptSettings.EnableFileStatus = $false

jacoco で lombok で生成されたコードを無視したい

https://www.rainerhahnekamp.com/en/ignoring-lombok-code-in-jacoco/ https://projectlombok.org/features/configuration https://qiita.com/hrkt/items/aa8b8434d4e30a6b160f

lombok.addLombokGeneratedAnnotation = true を追加すればよろしい。Jacoco 側にそういう対応が入っている。

https://github.com/jacoco/jacoco/pull/513

/actuator/configprops で kotlin の @ConfigurationProperties つけてるビーンが出ないよってとき

configprops は constructor の引数を見ている。何も指定しないと java と同じく arg0 とかになって acutuator で設定が見れない。

↓gradle の場合は以下のようにオプションを指定しよう。

            compileKotlin {
                kotlinOptions {
                    jvmTarget = "1.8"
                    allWarningsAsErrors = true
                    javaParameters = true // ←これ!
                }
            }

NullPointerException の stacktrace が出ないケース

https://stackoverflow.com/questions/2411487/nullpointerexception-in-java-with-no-stacktrace https://stackoverflow.com/questions/4659151/recurring-exception-without-a-stack-trace-how-to-reset

hotspot だと最適化の都合で、出ないケースがあるらしい。

-XX:-OmitStackTraceInFastThrow を入れると全部出るようになるが、パフォーマンスに若干の影響があるのかな。試してない。

→ パフォーマンスはあんま問題なさそうだが、ディスク溢れないように気をつけよう、という識者の意見。

Skitch が catalina で使えなくなったので頑張って OSX のスクショ機能を使っていた

OSX のスクショ機能なんであんな使いにくいの。。モーダルにしなくてよくない?? って苦しんでたけど、

↓これで skitch をまた使えるようになった。安心。 https://neko11.com/macos-catalina%E3%81%AB%E3%81%97%E3%81%A6%E3%81%8B%E3%82%89skitch%E3%81%A7%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%8F%E3%81%AA/