これは、なにをしたくて書いたもの?
PrometheusのNode.js向けのクライアントライブラリ(サードパーティ製のものですが)があるようなので、こちらを試して
みようかと。
Prometheus client for node.js
こちらのライブラリです。
GitHub - siimon/prom-client: Prometheus client for node.js
サードパーティ製ですが、Prometheusのドキュメントにも記載があります。
こちらを使うことで、Node.jsアプリケーションでも、Prometheusで扱える4種類のメトリクス(Counter、Gause、Histogram、Summary)を
独自に出力したり、またこのライブラリがデフォルトでNode.jsランタイムやOSから取得するメトリクスをエクスポートできるように
なるようです。
Prometheusのデータモデルや、メトリクスの種類についてはこちら。
このブログでは、以前にJVM Clientを使った時にこのあたりの用語については書いておきました。
PrometheusのJVM Clientを試してみる - CLOVER🍀
それで、このNode.jsクライアントですが、今回はExpressと一緒に使おうと思います。とはいえ、このライブラリが
特にWebアプリケーション用、というわけではありません。Registryのメソッドを使うことで、いつでもメトリクスを
出力することができます。
それでは、使っていってみましょう。お題は、以前に書いたJVM Clientのサンプルの簡易版にします。
環境
今回の環境は、こちらです。
$ node -v v10.15.3 $ npm -v 6.4.1
Prometheus client for node.jsをインストール。
$ npm i prom-client
Expressもインストールします。
$ npm i express
インストールされたバージョンはこちら。
"dependencies": { "express": "^4.16.4", "prom-client": "^11.2.1" },
サンプルプログラム
作成したプログラムは、こちら。
server.js
const prometheusClient = require("prom-client"); const collectDefaultMetrics = prometheusClient.collectDefaultMetrics; collectDefaultMetrics(); // デフォルトで組み込まれているメトリクスを、デフォルト10秒間隔で取得 // collectDefaultMetrics({ timeout: 5000 }); // デフォルトで組み込まれているメトリクスを、5秒おきに取得 const express = require("express"); const app = express(); let counter = 0; const callCounter = new prometheusClient.Counter({ name: "method_call_counter", help: "counter for method call count", labelNames: ["method", "path"] }); const incRequestSummary = new prometheusClient.Summary({ name: "inc_request_summary", help: "summary for inc method call summary", }); app.get("/metrics", (req, res) => { res.set("Content-Type", "text/plain"); res.send(prometheusClient.register.metrics()); }); app.get("/counter/current", (req, res) => { callCounter.labels("inc", "/counter/current").inc(); res.send(`current counter = ${counter}`); }); app.get("/counter/inc", (req, res) => { const end = incRequestSummary.startTimer(); try { counter++; callCounter.labels("inc", "/counter/inc").inc(); res.send(`increment counter = ${counter}`); } finally { end(); } }); app.listen(3000, () => console.log(`[${new Date()}] server startup.`));
ドキュメントに沿って、見ていきます。
利用するモジュールをrequireしておきます。
const prometheusClient = require("prom-client");
また、デフォルトで利用できるメトリクスを有効にします。
const collectDefaultMetrics = prometheusClient.collectDefaultMetrics; collectDefaultMetrics(); // デフォルトで組み込まれているメトリクスを、デフォルト10秒間隔で取得 // collectDefaultMetrics({ timeout: 5000 }); // デフォルトで組み込まれているメトリクスを、5秒おきに取得
デフォルトで収集されるメトリクスは、有効にしておくことが推奨されています。
ここでいう、「デフォルトのメトリクス」とは、このあたりですね。
const metrics = { processCpuTotal, processStartTime, osMemoryHeap, processOpenFileDescriptors, processMaxFileDescriptors, eventLoopLag, processHandles, processRequests, heapSizeAndUsed, heapSpacesSizeAndUsed, version };
イベントループやプロセスハンドル、Node.jsのバージョンなど、Node.js固有のメトリクスも含まれています。
https://github.com/siimon/prom-client/blob/v11.2.1/lib/defaultMetrics.js#L3-L13
https://github.com/siimon/prom-client/blob/v11.2.1/lib/defaultMetrics.js#L17-L29
デフォルトのメトリクスは実行する際にオプションを指定することができ、今回のコメントではメトリクスの取得間隔を
しているサンプルにしていますが、他にはメトリクスのprefixやメトリクスの登録対象のRegistryを指定したりできるようです。
collectDefaultMetrics(); // デフォルトで組み込まれているメトリクスを、デフォルト10秒間隔で取得 // collectDefaultMetrics({ timeout: 5000 }); // デフォルトで組み込まれているメトリクスを、5秒おきに取得
で、今回はExpressを使いつつ、カウンターを用意してカウントアップするAPIと現在のカウンターの値を取得するAPIを
用意してみましょう。
app.get("/counter/current", (req, res) => { callCounter.labels("inc", "/counter/current").inc(); res.send(`current counter = ${counter}`); }); app.get("/counter/inc", (req, res) => { const end = incRequestSummary.startTimer(); try { counter++; callCounter.labels("inc", "/counter/inc").inc(); res.send(`increment counter = ${counter}`); } finally { end(); } });
この時、メトリクスも登録していくようにします。今回は、CounterとSummaryの2つを使用しました。
let counter = 0; const callCounter = new prometheusClient.Counter({ name: "method_call_counter", help: "counter for method call count", labelNames: ["method", "path"] }); const incRequestSummary = new prometheusClient.Summary({ name: "inc_request_summary", help: "summary for inc method call summary", });
Counterは用意した2つのメソッド呼び出しの回数を、ラベルを使いつつ記録します。Summaryはカウントアップのみを対象に、
ラベルはなしで使います。
ラベルを使う場合は、このようにlabelsメソッドを介して目的のメソッドを呼び出します(Counterならincなど)。
app.get("/counter/current", (req, res) => { callCounter.labels("inc", "/counter/current").inc(); res.send(`current counter = ${counter}`); });
また、ドキュメントを見ると値の設定とともにラベルを指定することもできるようですね。
Summaryはstart/end方式のようです。ラベルは使っていません。
※一緒にCounterも使っています。
app.get("/counter/inc", (req, res) => { const end = incRequestSummary.startTimer(); try { counter++; callCounter.labels("inc", "/counter/inc").inc(); res.send(`increment counter = ${counter}`); } finally { end(); } });
他にもSummary#observeなどあるので、ドキュメントを参照してみてください。
登録しているメトリクスの出力には、Registry#metricsメソッドを使います。
今回は、Expressを使ってエンドポイントを設けました。
app.get("/metrics", (req, res) => { res.set("Content-Type", "text/plain"); res.send(prometheusClient.register.metrics()); });
あとは、このクラスを「npm start」で起動できるようにしましょう。
"scripts": { "start": "node server.js", "test": "echo \"Error: no test specified\" && exit 1" },
これで、サンプルプログラムの準備は完了です。
確認
とりあえず、起動してみます。
$ npm start
まずは、メトリクスを取得してみます。
$ curl localhost:3000/metrics $ curl localhost:3000/metrics # HELP process_cpu_user_seconds_total Total user CPU time spent in seconds. # TYPE process_cpu_user_seconds_total counter process_cpu_user_seconds_total 0.001038 1552119478037 # HELP process_cpu_system_seconds_total Total system CPU time spent in seconds. # TYPE process_cpu_system_seconds_total counter process_cpu_system_seconds_total 0.00026 1552119478037 # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. # TYPE process_cpu_seconds_total counter process_cpu_seconds_total 0.001298 1552119478037 # HELP process_start_time_seconds Start time of the process since unix epoch in seconds. # TYPE process_start_time_seconds gauge process_start_time_seconds 1552119478 # HELP process_resident_memory_bytes Resident memory size in bytes. # TYPE process_resident_memory_bytes gauge process_resident_memory_bytes 41758720 1552119478090 〜省略〜 # HELP method_call_counter counter for method call count # TYPE method_call_counter counter # HELP inc_request_summary summary for inc method call summary # TYPE inc_request_summary summary inc_request_summary{quantile="0.01"} 0 inc_request_summary{quantile="0.05"} 0 inc_request_summary{quantile="0.5"} 0 inc_request_summary{quantile="0.9"} 0 inc_request_summary{quantile="0.95"} 0 inc_request_summary{quantile="0.99"} 0 inc_request_summary{quantile="0.999"} 0 inc_request_summary_sum 0 inc_request_summary_count 0
いろいろくっついていますが、最後に自分が登録したメトリクスもすでに一部見えています。
デフォルトのメトリクスの最後についている数字ですが、これは値を設定した際のタイムスタンプです。
これは、自分で作成したメトリクスにも記録することができます(明示的に指定が必要で、今回は指定していません)。
Register#metricsを呼び出す際に、タイムスタンプを出力しないようにすることもできるようです。
それでは、今回作成したAPIを呼び出してみましょう。
$ curl localhost:3000/counter/inc increment counter = 1 $ curl localhost:3000/counter/inc increment counter = 2 $ curl localhost:3000/counter/inc increment counter = 3 $ curl localhost:3000/counter/current current counter = 3 $ curl localhost:3000/counter/inc increment counter = 4 $ curl localhost:3000/counter/inc increment counter = 5 $ curl localhost:3000/counter/inc increment counter = 6 $ curl localhost:3000/counter/current current counter = 6
「/counter/inc」を6回、「/counter/current」を2回呼び出してみました。
メトリクスを取得してみます。
$ curl localhost:3000/metrics 〜省略〜 # HELP method_call_counter counter for method call count # TYPE method_call_counter counter method_call_counter{method="inc",path="/counter/inc"} 6 method_call_counter{method="inc",path="/counter/current"} 2 # HELP inc_request_summary summary for inc method call summary # TYPE inc_request_summary summary inc_request_summary{quantile="0.01"} 0.000247481 inc_request_summary{quantile="0.05"} 0.000247481 inc_request_summary{quantile="0.5"} 0.000328285 inc_request_summary{quantile="0.9"} 0.002689974700000001 inc_request_summary{quantile="0.95"} 0.002912271 inc_request_summary{quantile="0.99"} 0.002912271 inc_request_summary{quantile="0.999"} 0.002912271 inc_request_summary_sum 0.004805055 inc_request_summary_count 6
今回作成したCounterでは、各メソッドの呼び出し回数がラベルとともに表示され
method_call_counter{method="inc",path="/counter/inc"} 6 method_call_counter{method="inc",path="/counter/current"} 2
Summaryでは呼び出し回数と分位数(quantile)が表示されています。
inc_request_summary{quantile="0.01"} 0.000247481 inc_request_summary{quantile="0.05"} 0.000247481 inc_request_summary{quantile="0.5"} 0.000328285 inc_request_summary{quantile="0.9"} 0.002689974700000001 inc_request_summary{quantile="0.95"} 0.002912271 inc_request_summary{quantile="0.99"} 0.002912271 inc_request_summary{quantile="0.999"} 0.002912271 inc_request_summary_sum 0.004805055 inc_request_summary_count 6
まとめ
今回は、PrometheusのNode.js向けのクライアント(Prometheus client for node.js)を試してみました。
メトリクスの出力方法が最初わかりにくいかな?と思ったくらいで、扱っている概念はPrometheusのものなのでそれほど苦労せず
使えました。
これで、Node.jsアプリケーションでもPrometheus向けにメトリクスがエクスポートできるようになりましたね。