この記事は「はてなエンジニア Advent Calendar 2024 - Hatena Developer Blog」の36日目の記事です。昨日は id:hogashi さんの Redashではクエリ結果にHTMLを使えるので便利 長いカラムをdetailsで畳める ほか - hogashi.* でした。
わたしはプライベートで身内用の Minecraft サーバーを運用しているのですが、プレイヤー数やデータサイズなどを Mackerel に送信して監視しているので、その話をやっていきます。
Minecraft サーバーには、 UT3 (or GameSpot) Query Protocol という、サーバーの状態を取得するためのプロトコルが実装されており、サーバー側の設定で有効にしていれば、ネットワーク越しにプレイヤー数やレイテンシーなどを取得することが可能です。 今回はこれで得られる情報をプラグイン経由で Mackerel に送信します。完成品はこちら:
まず、 Mackerel プラグインのひな形を作っていきましょう。 必要なのは3つのメソッドで、以下のように実装します:
package main import ( "flag" mp "github.com/mackerelio/go-mackerel-plugin" ) // MinecraftPlugin Mackerel Agent type MinecraftPlugin struct {} // メトリクスデータを取得する func (mc MinecraftPlugin) FetchMetrics() (map[string]float64, error) { return map[string]float64{}, nil // あとで実装する } // どのようにグラフで表すかを定義する func (mc MinecraftPlugin) GraphDefinition() map[string]mp.Graphs { return map[string]mp.Graphs{} // あとで実装する } // プレフィクスを取得する、今回は固定値 func (mc MinecraftPlugin) GetPrefix() string { return "minecraft"; } func main() { tempfile := flag.String("tempfile", "", "Temp file name") flag.Parse() mc := MinecraftPlugin{} helper := mp.NewMackerelPlugin(mc) helper.Tempfile = *tempfile helper.Run() }
次に、先に述べたプロトコルを叩いてサーバー情報を得ましょう。これは都合の良いライブラリがすでに存在していたので、そちらを使うことで、以下のように取得できます:
import ( "github.com/sch8ill/mclib" ) type ServerStatus struct { Latency int MaxPlayers int OnlinePlayers int } func GetServerStatus(address string) (*ServerStatus, error) { client, err := mclib.NewClient(address) // minecraft.game.natsuneko.cat:25565 などのアドレス if err != nil { return nil, err } res, err := client.StatusPing() if err != nil { return nil, err } return &ServerStatus{ Latency: res.Latency, MaxPlayers: res.Players.Max, OnlinePlayers: res.Players.Online, }, nil }
これで現在のサーバーレイテンシー、オンラインプレイヤー数、設定上の最大プレイヤー数が取得できました。 今回は使用していませんが、 MOTD やバージョン、サーバー実装ソフトウェアの名前なども取得することが出来ます。
次は、 GraphDefinition
を変更します。これは単純に Integer を返す実装にしてしまいましょう。
func (mc MinecraftPlugin) GraphDefinition() map[string]mp.Graphs { prefix := mc.GetPrefix() player := prefix + ".player" return map[string]mp.Graphs{ player: { Label: "Minecraft Server Status", Unit: mp.UnitInteger, Metrics: []mp.Metrics{ {Name: "max", Label: "Limit"}, {Name: "online", Label: "Current Players"}, {Name: "latency", Label: "Latency"}, }, }, } }
今回の場合は、ラベル名として Minecraft Server Status
、そこに表示される値として最大プレイヤー数 max
、現在のプレイヤー数 online
、レイテンシーとして latency
を返すようにします。
このようにすることで、画像のようなデータが取得できます。
今回はダッシュボードで別途表示を整えてしまうのでまとめてしまっていますが、プレイヤー数とレイテンシーでは単位が異なるので、分けておくと良いですね。
最後に値を返しましょう。次のような実装を FetchMetrics
に追加することで、送信するメトリクスデータを出力することが出来ます。
func (mc MinecraftPlugin) FetchMetrics() (map[string]float64, error) { status, err := GetServerStatus(mc.GetServerAddress()) // 先ほどのモジュールを呼び出す、 GetServerAddress はアドレスを返す関数を定義する if err != nil { return nil, err } return map[string]float64{ "max": float64(status.MaxPlayers), "online": float64(status.OnlinePlayers), "latency": float64(status.Latency), }, nil }
最後に、ビルドしたファイルを適当な場所に配置し、 /etc/mackerel-agent/mackerel-agent.conf
の最後に以下を追記すれば完成です:
[plugin.metrics.minecraft] command = "/opt/mackerel-agent/plugins/bin/mackerel-plugin-minecraft"
これでしばらく待つとメトリクスデータが表示されます。お疲れさまでした。 最後に、 Minecraft サーバーでもこのような監視をするメリットとして、適切なスペックを選べるようになることが挙げられます。 例えば、次のグラフは四角で囲まれた時間帯はプレイヤーが1人オンラインとして遊んでいたのですが、
プレイヤーが存在することで何らかの処理が行われた結果、 CPU 使用率が +50% 消費されていることが分かります。 では、単純にプレイヤー1人につき CPU を 50% 消費するのか、というのも正確に求めるには計測する必要があるのですが、こちらも次のグラフを見ると:
実際にはそういうことは無く、プレイヤーが2人、3人となったとしても、 CPU 使用率は大きくは変わらないということが分かるかと思います。 このように、プレイヤー数と CPU 使用率・メモリ使用率が記録されていることで、適切なサーバースペックであるか、などがある程度計測によって求められるようになります。 計測大事。
ということで、アドベントカレンダー36日目の記事でした。