WebSphere LibertyのMP MetricsデータをCSVで出力するシェル・プログラム

WebSphere LibertyのMicroProfile Metricsを使用すると、RESTコールで、簡単にLibertyのパフォーマンス・データを取得することができます。ただ、パフォーマンスの応答が、Prometheus Text形式であり、Prometheusなどのツールと連携する場合には、問題ありませんが、Prometheusが無い環境では、グラフ化をしたい場合に、手間がかかると思います。
LibertyのMP Metricsの応答データを、CSV出力するシェル・プログラムを紹介します。CSVに変換することで、エクセルなどを用いて、必要なパフォーマンス・データを容易にグラフ化できます。
# MP Metricsを取得するためのLibertyの設定
MP Metricsを有効化し、RESTコールで、Metrics(パフォーマンス)データを取得できるようにするためには、Libertyのserver.xmlに下記の定義を追加します。
```server.xml
<server>
    <featureManager>
        <feature>mpMetrics-5.0</feature>
    </featureManager>
    <mpMetrics authentication="false"/>
</server>
```
`<mpMetrics authentication="false"/>`を追加することで、認証なしで、Metricsデータを取得することができます。認証ありでの設定についてはOpen Libertyのドキュメントを参照ください。
[Open Libertyドキュメント - MicroProfile Metrics](https://openliberty.io/docs/latest/reference/feature/mpMetrics-5.1.html)
上記の設定を行ったLibertyサーバーに対して、/metricsのURLにGETリクエストを送信すると、下記のようにPrometheus Text形式の応答が返ります。
```
$ curl http://localhost:9080/metrics
# HELP servlet_responseTime_total_seconds The total response time of this servlet since the start of the server.
# TYPE servlet_responseTime_total_seconds gauge
servlet_responseTime_total_seconds{mp_scope="vendor",servlet="io_openliberty_microprofile_metrics_5_0_public_internal_PublicMetricsRESTProxyServlet",} 0.06364990000000001
# HELP classloader_loadedClasses_total Displays the total number of classes that have been loaded since the Java virtual machine has started execution.
# TYPE classloader_loadedClasses_total counter
classloader_loadedClasses_total{mp_scope="base",} 10576.0

// 途中略

# HELP gc_total Displays the total number of collections that have occurred. This attribute lists -1 if the collection count is undefined for this collector.
# TYPE gc_total counter
gc_total{mp_scope="base",name="global",} 0.0
gc_total{mp_scope="base",name="scavenge",} 73.0
# HELP requestTiming_slowRequestCount The number of servlet requests that are currently running but are slow.
# TYPE requestTiming_slowRequestCount gauge
requestTiming_slowRequestCount{mp_scope="vendor",} 0.0
# HELP session_liveSessions The number of users that are currently logged in.
# TYPE session_liveSessions gauge
session_liveSessions{appname="default_host_metrics",mp_scope="vendor",} 1.0
$
```
#で始まる行は、Metrics項目の説明と、Metrics値の種類で、補足です。実際のMetricsの項目名と値は、#のない行です。
# MP MetricsのデータをCSVに変換するシェル・プログラム
下記に、/metricsのURLを定期的に呼び、取得したMetricデータをCSV形式でファイルに記録するサンプル・シェル・プログラム(metrics2csv.sh)を紹介します。
実行方法は、下記のように、第一引数にmetricの取得間隔(秒)を指定し、標準出力をリダイレクトすることで、ファイルにCSV形式でMetricsデータを記録できます。計測を停止するには、[Ctrl]+[c]で停止してください。
```
$ ./metrics2csv.sh 30 > ./csv/metrics_myhostname_202403051935.csv
```
出力結果は、下記のようになります。
```
$ cat ./csv/metrics_myhostname_202403051935.csv
Shell start date = Tue Mar 5 19:35:03 JST 2024
Shell running host = myhost
Connect host = xxx.xxx.xxx.xxx
MPMetrics retrieve interval = 30 sec
--------------------
Metrics item num = 31
--------------------
date, time, classloader_loadedClasses_count{mp_scope="base";}, classloader_loadedClasses_total{mp_scope="base";}, classloader_unloadedClasses_total{mp_scope="base";}, cpu_availableProcessors{mp_scope="base";}, cpu_processCpuLoad_percent{mp_scope="base";}, cpu_processCpuTime_seconds{mp_scope="base";}, cpu_systemLoadAverage{mp_scope="base";}, gc_time_seconds_total{mp_scope="base";name="global";}, gc_time_seconds_total{mp_scope="base";name="scavenge";}, gc_total{mp_scope="base";name="global";}, gc_total{mp_scope="base";name="scavenge";}, jvm_uptime_seconds{mp_scope="base";}, memory_committedHeap_bytes{mp_scope="base";}, memory_maxHeap_bytes{mp_scope="base";}, memory_usedHeap_bytes{mp_scope="base";}, requestTiming_activeRequestCount{mp_scope="vendor";}, requestTiming_hungRequestCount{mp_scope="vendor";}, requestTiming_requestCount_total{mp_scope="vendor";}, requestTiming_slowRequestCount{mp_scope="vendor";}, servlet_request_total{mp_scope="vendor";servlet="io_openliberty_microprofile_metrics_5_0_public_internal_PublicMetricsRESTProxyServlet";}, servlet_responseTime_total_seconds{mp_scope="vendor";servlet="io_openliberty_microprofile_metrics_5_0_public_internal_PublicMetricsRESTProxyServlet";}, session_activeSessions{appname="default_host_metrics";mp_scope="vendor";}, session_create_total{appname="default_host_metrics";mp_scope="vendor";}, session_invalidated_total{appname="default_host_metrics";mp_scope="vendor";}, session_invalidatedbyTimeout_total{appname="default_host_metrics";mp_scope="vendor";}, session_liveSessions{appname="default_host_metrics";mp_scope="vendor";}, thread_count{mp_scope="base";}, thread_daemon_count{mp_scope="base";}, thread_max_count{mp_scope="base";}, threadpool_activeThreads{mp_scope="vendor";pool="LargeThreadPool";}, threadpool_size{mp_scope="vendor";pool="LargeThreadPool";}
2024/03/05, 19:35:03, 10578.0, 10578.0, 0.0, 16.0, 5.078023396707402E-5, 20.671875, -1.0, 0.0, 0.258, 0.0, 80.0, 10409.406, 1.37756672E8, 5.36870912E8, 5.638344E7, 1.0, 0.0, 7.0, 0.0, 6.0, 0.0883503, 0.0, 1.0, 1.0, 1.0, 0.0, 257.0, 254.0, 305.0, 3.0, 200.0
2024/03/05, 19:35:33, 10578.0, 10578.0, 0.0, 16.0, 3.250790757920611E-5, 20.6875, -1.0, 0.0, 0.258, 0.0, 80.0, 10439.448, 1.37756672E8, 5.36870912E8, 5.700348E7, 1.0, 0.0, 8.0, 0.0, 7.0, 0.0923153, 0.0, 1.0, 1.0, 1.0, 0.0, 257.0, 254.0, 305.0, 3.0, 200.0
2024/03/05, 19:36:03, 10578.0, 10578.0, 0.0, 16.0, 0.0, 20.6875, -1.0, 0.0, 0.258, 0.0, 80.0, 10469.475, 1.37756672E8, 5.36870912E8, 5.8364896E7, 1.0, 0.0, 9.0, 0.0, 8.0, 0.095949, 0.0, 1.0, 1.0, 1.0, 0.0, 257.0, 254.0, 305.0, 3.0, 200.0
2024/03/05, 19:36:33, 10578.0, 10578.0, 0.0, 16.0, 6.50493913929816E-5, 20.71875, -1.0, 0.0, 0.258, 0.0, 80.0, 10499.501, 1.37756672E8, 5.36870912E8, 6.053168E7, 1.0, 0.0, 10.0, 0.0, 9.0, 0.0994072, 0.0, 1.0, 1.0, 1.0, 0.0, 257.0, 254.0, 305.0, 3.0, 200.0
$
```
metrics2csvの内容を下記に添付しますので、自由に変更して活用ください。
```metrics2csv.sh
#!/bin/bash

# 実行方法:
#   ./metrics2csv.sh ([metric取得間隔(秒)]) (> [結果出力ファイル名])
# 実行例: 
#   ./metrics2csv.sh 30 > metrics_hostname_hhmmss.csv
# 終了方法: 
#   [Ctrl]+[c]
# 実行例(バックグラウンド):
#   nohup ./metrics2csv.sh 30 > metrics_hostname_hhmmss.csv &
# 終了方法(バックグラウンド):
#   ps -ef | grep metrics2csv
#   kill <psコマンドで確認したプロセスID>

# 第一引数をメトリック取得の間隔に設定。引数が無い場合は60秒間隔とする。
if [ "$#" -eq 0 ]; then
  interval=60
elif [ "$#" -eq 1 ] && [[ "${1}" =~ ^[0-9]+$ ]]; then
  interval=${1}
else
  echo "エラー: 引数は、metric取得間隔の1個、または、0個(取得間隔は60秒)を指定します．" >&2
  echo "実行方法: ./metrics2csv.sh ([metric取得間隔(秒)]) (> [結果出力ファイル名])"  >&2
  echo "実行例: ./metrics2csv.sh 30 > metrics_hostname_hhmmss.csv"  >&2
  exit 1
fi

echo "Shell start date = "$(date)
echo "Shell running host = "$(hostname)

# localhost
connecthost=localhost
# remote server
# connecthost=xxx.xxx.xxx.xxx

echo "Connect host = ""$connecthost"
echo "MPMetrics retrieve interval = ""$interval"" sec"

# Metric項目数
lastitemnum=0

# ここからループ
while true; do

  # dateコマンドで、日付と時刻部分を取得
  yyyymmdd=$(date '+%Y/%m/%d')
  hhmmss=$(date '+%H:%M:%S')

  # MPMetricの取得
  raw_metrics=$(curl -s http://"$connecthost":9080/metrics)

  # 必要な行のみ抽出 #で始まる項目の説明行を削除して、ソート
  row=$(echo "$raw_metrics" | egrep -v "^#" | sort)

  # MPMetricの項目数
  itemnum=$(echo "$row" | wc -l)

  # 「} 」で区切って、項目名と値を分離する
  # ヘッダー行は、最初の1回目のリクエストと、もし項目数が変わった時だけ、出力する
  if [ $((itemnum)) -ne $((lastitemnum)) ]; then

    echo "--------------------"
    echo "Metrics item num = ""$itemnum"
    echo "--------------------"

    lastitemnum="$itemnum"

    # 「}」で区切った前半が項目名。項目名の中の「,」は「;」に置き換え。CSVファイルとして開きやすくするため。
    item=$(echo "$row" | sed "s/,/;/g" | awk '{print ", " substr($0,1,index($0,"}") )}')

    # 項目の一、二列目は日付と時間とし、その後、項目名を出力
    echo "date, time""$item" | sed -z "s/\n//g"
    echo
    
  fi

  # 「}」で区切った後半が値
  value=$(echo "$row" |  awk '{print ", " substr($0,index($0,"}")+2 )}')

  # 一、二列目はcurl呼び出しの日付と時間を挿入し、その後、項目値を出力
  echo "$yyyymmdd"", ""$hhmmss""$value" | sed -z "s/\n//g"
  echo

  # コマンド引数で指定した秒数sleepして無限ループ
  sleep $interval

done
```
上記で掲載したサンプルの出力結果は、ほとんどLibertyをインストールしただけで、まだ、アプリケーションがデプロイされていない状態のLibertyの出力例なので、Metricsの項目数が、31と少ないですが、実際にアプリケーションがデプロイされると簡単に数百の項目数になります。
実際にMetricsデータを取得し、出力するデータを抽出しているのは、下記の部分になります。
```metrics2csv-part.sh
  # MPMetricの取得
  raw_metrics=$(curl -s http://"$connecthost":9080/metrics)

  # 必要な行のみ抽出 #で始まる項目の説明行を削除して、ソート
  row=$(echo "$raw_metrics" | egrep -v "^#" | sort)
```
出力項目を、特定の項目にしぼる場合、例えば、HTTPセッションとスレッド・プールのデータ項目のみを出力する場合には、下記のように抽出条件を修正します。
```metrics2csv-part2.sh
row=$(echo "$raw_metrics" | egrep -v "^#" | egrep "session|threadpool" | sort)
```
MP Metricsで取得できるデータ項目については、下記のドキュメントを参照ください。
[Open Libertyドキュメント - MicroProfile Metrics 5.0 metrics reference](https://openliberty.io/docs/latest/metrics-list.html#_microprofile_metrics_5_0_metrics_reference)
どのメトリックを見るべきかは、下記のQiitaの記事も参考になります。
[Qiita - Libertyの性能情報をPythonのSidecarで取得する（mpMetrics）](https://qiita.com/sotoiwa/items/113258e8ac52b676b791)