New Relic プラグイン開発入門 (Java 編)

New Relic が New Relic Platform と銘打って、
様々なミドルウェアの監視をプラグインで追加できるようになりました。

当然プラグインを開発するための API も提供されています。
実際にプラグインを作ってみたので共有します。

New Relic プラグインの特徴

  • LITE (無料) プランでも利用可能
    • ただし、LITE プランだとデータの保存期間が24時間になる
  • 言語はなんでも可。RubyJava はサンプルがあるので楽。
  • 時系列で数字が決まるデータであれば、なんでも記録可能
    • 例えば自分の体重の推移を記録することも技術的にはできる
  • プラグインは公開しなくても利用可能

シンプルな仕組み故に柔軟性が高く、扱いやすい印象です。

今回開発するプラグイン

New Relic LITE プランでは、Java メモリ使用率のグラフが見えません。
PRO 以上のプランであれば、Monitoring → JVMs で見られるので
通常は課金するところですが、貧乏なのでプラグインにしてみましょう。
ただし、対象は Tomcat のみです。

こんな感じで見られます。

環境

プラグイン開発

準備

Java で書く場合には、New Relic が公開しているサンプルを改造していくのが楽です。
GitHub - newrelic-platform/waveform_example_plugin にアクセスして、クローンします。

ディレクトリ構造は以下のようになっています。

│  .gitignore
│  build.xml
│  LICENSE
│  README.md
│
├─config
│      .gitignore
│      com.newrelic.examples.waveform.json
│      example_logging.properties
│      template_newrelic.properties
│
├─lib
│      metrics_publish-1.2.0.jar
│
└─src
    └─com
        └─newrelic
            └─examples
                └─waveform
                        Main.java
                        Waveform.java
                        WaveformFactory.java

主にに記述することになる Java ファイルは3つしかないので簡単な気配が漂っていますね。

次にやることはプラグインの GUID を決めることです。
この GUID はプラグインを一意に特定するために必要なので、他者と重複しないように決めます。
今回は、org.codefirst.newrelic.plugins.tomcat にしました。
ここで注意しなければならないのは、この GUID の . で区切った最後の部分(上の例では tomcat )がメニューのラベルになるということです。

こんな感じ。メニューになることを前提にカッコイイ名前にしましょう。

GUID を決めたら、Waveform.java の _TYPE_YOUR_GUID_HERE_ を書き換えます。

public Waveform(String name, int sawtoothMax, int squarewaveMax) {
    super("_TYPE_YOUR_GUID_HERE_", "1.0.2");
    ...
}

では Waveform.java にある getComponentHumanLabel は何に使われるのでしょうか。

@Override
public String getComponentHumanLabel() {
   return name;
}

getComponentHumanLabel の戻り値は、メニューをクリックした後のラベルに使われます。下の例では Default にしています。

例えば、ひとつのサーバで Tomcat が複数起動していた場合に、
それらを見分けるためにラベルを付けられるイメージです。
(例:Tomcat:8080, Tomcat:18080)

そのため、設定ファイルから変更できるようにしておくべきです。
設定ファイルの利用については後述します。

ここでパッケージ名とクラス名を変更しておきます。

  • com.newrelic.examples.waveform → org.codefirst.newrelic.plugins.tomcat
  • Waveform → Tomcat
Tomcat のメモリ状況の取得

Tomcat は 7.0.33 以降で、Tomcat Manager からメモリの状況を少し詳しく取得できるようになりました。
$CATALINA_HOME/conf/tomcat-users.xml

<role rolename="manager-status"/>
<user username="newrelic" password="newrelic" roles="manager-status"/>

を追加し、

http://localhost:8080/manager/status?XML=true

に newrelic ユーザでアクセスすると、

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/manager/xform.xsl" ?>
<status>
 <jvm>
  <memory free='236926992' total='257032192' max='3814064128'/>
  <memorypool name='PS Eden Space' type='Heap memory' usageInit='67108864' usageCommitted='67108864' usageMax='1407975424' usageUsed='6675432'/>
  <memorypool name='PS Old Gen' type='Heap memory' usageInit='178782208' usageCommitted='178782208' usageMax='2860515328' usageUsed='2303360'/>
  <memorypool name='PS Survivor Space' type='Heap memory' usageInit='11141120' usageCommitted='11141120' usageMax='11141120' usageUsed='11126408'/>
  <memorypool name='Code Cache' type='Non-heap memory' usageInit='2555904' usageCommitted='2555904' usageMax='50331648' usageUsed='1182464'/>
  <memorypool name='PS Perm Gen' type='Non-heap memory' usageInit='21757952' usageCommitted='21757952' usageMax='85983232' usageUsed='15687048'/>
 </jvm>
 ...
</status>

のような XML が取得できます。
Tomcat を監視するプログラム(Agent と呼びます)はこの URL をポーリングして、
値を New Relic サーバに送信し続けます。
実際の取得部分のコードは TomcatStatus#fetch を参照してください。

データの送信

取得したデータを送信するためには、Agent#pollCycle の中で Agent#reportMetric を呼び出します。
Tomcat.java (元 Waveform.java) の中の pollCycle にコードを書いて行きます。

こんな感じにしてみました。

@Override
public void pollCycle() {
    TomcatStatusResult result = this.tomcatStatus.fetch();

    if (result.getMemory() != null) {
        reportMetric("Tomcat/Memory/Total", "percent", result.getMemory().getPercent());
    }

    for (MemoryPool memoryPool : result.getMemoryPools()) {
        reportMetric("Tomcat/Memory/" + memoryPool.getName(), "percent", memoryPool.getPercent());
    }

    if (result.getThreadInfo() != null) {
        reportMetric("Tomcat/Thread/CurrentThreadsBusy", "threads", result.getThreadInfo().getCurrentThreadsBusy());
    }
}

reportMetric メソッドの第一引数は、メトリクス名、第二引数は単位です。第三引数には値を指定します。
メトリクス名は、コンポーネント名/任意の名前 の形式にするようです。
例えば、"Tomcat/Memory/Total" を指定した場合には、

のように表示されます。

また、単位に "threads" を指定した場合には、

のように表示されるようです。
"percent" を指定すると "%" に変換してくれたりもします。

pollCycle メソッドは、1分毎に呼び出されるためポーリング自体の処理を書かなくて楽です。

設定ファイル

プラグインには設定を読む込む仕組みが必要不可欠ですが、そこも楽できるような仕組みが備わっています。

TomcatFactory.java (元 WaveformFactory.java) に以下のように記述します。

public class TomcatFactory extends AgentFactory {
    public TomcatFactory() {
        super("org.codefirst.newrelic.plugins.tomcat.json");
    }

    @Override
    public Agent createConfiguredAgent(Map<String, Object> properties) {
        String name = (String) properties.get("name");
        String host = (String) properties.get("host");
        int port = ((Long) properties.get("port")).intValue();
        ...
        return new Tomcat(name, host, port, ...);
    }
}

スーパークラスのコンストラクタに渡しているのは設定ファイル名です。
config/org.codefirst.newrelic.plugins.tomcat.json を読み込むように指定しています。

設定ファイルの内容は以下のような感じです。

[
  {
    "name" : "Default",
    "host" : "127.0.0.1",
    "port" : 8080,
    ...
  }
]

前述の通り、複数の Tomcat に対応できるように配列を渡しています。
あとは createConfiguredAgent の中で設定値を読み取り、Agent クラスを new するだけです。

ビルド

ここまでで、実装が完了したのでビルドしてみます。
build.xml の中の Waveform を Tomcat に適当に置換し、以下のコマンドを実行します。

$ ant

すると dist/tomcat-x.y.z.tar.gz とファイルが作られます。これが成果物です。

デプロイ

tomcat-x.y.z.tar.gz を Tomcat が動作しているサーバに送り、適当なディレクトリに展開します。
前述の通り $CATALINA_HOME/config/tomcat-users.xml の設定を行い、Tomcat を再起動します。

config/template_newrelic.properties を config/newrelic.properties にコピーし、

licenseKey = YOUR_LICENSE_KEY_HERE

の YOUR_LICENSE_KEY_HERE を New Relic のライセンスキーで置換します。

以下のコマンドで Agent を実行します。

$ java -jar tomcat-x.y.z.jar

この後しばらく待てば、New Relic 側にメニューが増えているはずです。

本来は OS の起動時に自動で Agent が起動するように設定するべきですが、この記事では割愛します。

まとめ

  • New Relic プラグインは簡単、楽しい
  • Java で書くのも簡単、楽しい
  • LITE プランでも作れるし、非公開でも使えるからぜひ作ってみてね

今回作成したプラグインは以下で公開しています。
GitHub - mallowlabs/newrelic_tomcat_plugin: A Tomcat metrics for New Relic Platform

もし、改良して使う場合には GUID を変更してお使いください。