RailsとScalaとインフラの三刀流と言えば聞こえはいいけど実態は雑用係に近いぽんこつです。最近はPlayframework/ScalaのプロジェクトのExecutionContextの状態を監視しようと色々やってて、その第一段としてExecutionContextの状態を取得するところからやります。
ExecutionContextとは
ScalaにはExecutionContextという並行並列実行に使う便利なものがありまして、適当にRunnableなクラスを投げ込むとmainとは別のスレッドでよしなに実行してくれます。便利ですね。また、そのようなものなので、殆どの場合、実装として内部にThreadPoolを持ってます。みんながよく書いてる
import scala.concurrent.ExecutionContext.Implicits.global
これはデフォルトで生成されるExecutionContextをimplicitで読めるようにするための魔法のimportです。勿論このようなものを自作することができて、以下のように作れます
val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(64))
便利ですね。上記で作っているExecutionContextの内部のThreadPoolはThread数が64で固定されたもので、Executorsの各種メソッドを呼び出せば、全く違うようなものを作ることができます。ちなみに、ExecutorsはJavaAPIなので、JavaでもExecutionContextが無い以外は同じようなことができます。
モチベーション
ここからが本題ですが、先程作ったExecutionContext、Thread数が64固定で設定しましたが、このThreadPoolがどの程度使われているか興味ありませんか。ぼくは大変興味があります。これが分かるとThread枯渇系の障害対応もできますし、パラメータチューニングも捗りそうです。計測できるようにしましょう。
Threadのリストを取得する
Java APIには全ThreadのStackTraceのリストを取得するAPIがあります。
Thread.getAllStackTraces(); // 返り値はMap[Thread, Array[StackTraceElement]
これを使えば現在動いてる全Threadの状態が取れます。StackTraceも同時に取得していてオーバヘッドが気になりますが、今は気にしないことにしましょう。Threadからは以下のような情報が取れます。
- id
- name
- state
- priority
stateを見るとWAITING/RUNNABLEなどの情報が取れます。特定のThreadPoolで使われているThreadのWAITINGとRUNNABLEの比率を見ると、そのThreadPoolの忙しさ具合が取れそうです。
問題はこのThreadのリストは本当に全てのThreadを含んでいて、GC用に確保されたThreadも数に含まれていたりします。厄介なのでnameでフィルターできると便利そうです。
ExecutionContextが立てるThreadに名前を付ける
実はExecutionContextが内部で使うThreadPoolのThreadには上記で取得できる名前を任意に付けることができます。Executors.newHogeThreadPoolの最後の引数にはThreadFactoryというclassを渡すことができます。
Executors.newFixedThreadPool(64, threadFactory);
自分で好きな名前を付けるThreadFactoryを作り、生成するThreadに好きな名前を付けられるようにしましょう。
import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicInteger class NamedThreadFactory(name: String) extends ThreadFactory { private val counter: AtomicInteger = new AtomicInteger override def newThread(runnable: Runnable): Thread = { val tName = s"${name}-${counter.incrementAndGet()}" new Thread(null, runnable, tName) } }
このようにnewThreadメソッドをoverrideして任意のThreadを返すようにすればOKです。上記のclassは${name}-${number}みたいな名前のThreadを自動で生成してくれるScalaのclassです。
あとは先程の方法でThreadのリストを取得し、名前で分類すれば、特定ThreadPoolのThreadのみを集めることができます。ちなみに私の場合はRunnableとそれ以外の状態のThreadの数を取得するWebAPIを作りました。これを使って可視化するところまでやる話は完全にインフラの話になるので、後編にまわします。お楽しみに!