1つのsbt projectでjavaagentの定義と利用をするsample。あるいはsbtのTests.Setupの代替案

JVMにはjavaagentという特別な仕組みがあるのを知ってますか?

自分が雑に思いついた具体例を書いておくと、例えばdatadogのtrace用のSDKがjavaagentで実装されてますね。 使用例は色々あるかもしれませんが、javaagentの例や、それの仕組みを詳細に解説するのが今回の目的ではないので、そこの詳細は各自ググってください。

すごく大雑把にいうと、特定の規約に従った普通とは違ったclassを定義して、そのpathを -javaagent: という引数とともに渡すと、普通のmainより先に特別に呼ばれる、という感じだと思います。 (正確には、単に先に呼ばれるより面白いことが出来るはずだが、今回はそこは使わないので説明割愛)

また、

github.com

こういったsbt pluginもありますが

「今回はこれの話ではありません!」

もちろん、このplugin自体は場合によって有用ですが、微妙に違う観点の話をするからです。

sbt-javaagent pluginは、既存のjavaagentを利用する際のpluginだと思われますが、今回のテーマは

「1つのsbt projectでjavaagentの定義と利用」

です。

「そもそも、そんなことしたい場面ある?」

というツッコミはありえます。その場面の話は後でするとして、ひとまずその方法を雑に説明するというか、実際のコードを書いたので、それを貼り付けます。

https://github.com/xuwei-k/javaagent-sbt-example/commit/84bd5c3644892d855af9f061d03ef6d781a3c4c3

ざっくり解説をすると

  • javaagentは依存ライブラリ使って頑張ることも不可能ではないらしいが、今回は面倒なのでpure javaでライブラリなしで書いた
  • するとScalaの依存も必要ないのでautoScalaLibraryやcrossPathsの設定をしてある
  • javaagent作るのに絶対必要なものとしてsbtで packageOptions += Package.ManifestAttributes("Premain-Class" -> "ここに名前") を追加
  • 利用する側は (agentのプロジェクト / Compile / packageBin).value.getCanonicalPath で得たpathを s"-javaagent:${agentJar}=${arg}" のように javaOptions に渡す
  • javaagent利用する場面は、testやrunなどいくつかあり得るが、いずれにせよforkしないと意味ないので、今回はtestなので Test / forktrue に設定
  • こうやって1つのsbt projectに収めてしまえば、特に明示的にjavaagentのjarをpublishLocalなどしなくても、これで自動で使えて便利

という感じです。

そもそもそんなことしたい場面ある?

の件ですが

今回はtest

と書きましたが、案外テストで使ったら便利な場面あるのでは?という、雑な思いつきです。

こちら公式ドキュメントからの引用ですが

https://www.scala-sbt.org/1.x/docs/Testing.html

When forking, the ClassLoader containing the test classes cannot be provided because it is in another JVM. Only use the () => Unit variants in this case.

とあります。よく考えれば当たり前ですね。

ではsbtやテストライブラリ側で、その他に「汎用的で便利な必ず実行前に同じJVM上でフック出来る仕組み」が存在するのか?というと、自分は知りません。 むしろ実は存在するなら教えてください。

そこで今回のように雑にjavaagent使ってみる例なわけですが

  • 必ず同じJVMで起動される
  • 必ず全てのテストの実行開始より前に処理を挟み込める

というのは便利だと思いませんか?具体的に何をするか?というと、それはもちろん例えばDatabaseの初期化処理といった Tests.Setup と同じような用途なわけですが、 頑張れば他のJVMからの処理で不可能ではない場合があるとしても、同じJVMで実行した方が都合がいい場合も多々ありますよね?

また、(本格的にやるなら既存の仕組みを使えばいいが)、簡易的にJVM自体のthreadやmemoryや、その他(同じJVM内部でないと取得が面倒な)色々な状態を継続的に取りたい、といった場合も、それ用の定期的に起動するcron的な処理をjavaagent側で挟んでしまうと便利かもしれません。

sample作ってみた程度で、これ以上の何かがあるわけではないので、これでおしまいです。

単に Tests.Setup の代替以上に、工夫次第では面白い便利なことが出来ると思うので、何か思いついたら作ってみてください。