0 フォロワー · 9 投稿

視覚化とは、データまたは情報をグラフィックスに含まれる視覚オブジェクト(ポイント、ライン、バーなど)としてエンコードすることで、そのデータまたは情報を通信するために使用される技術を指します。 目標は、ユーザーに情報を明確かつ効率的に伝達することです。

記事 Toshihiko Minamoto · 10月 5, 2023 4m read

コミュニティの皆さん、こんにちは!

DeepSee Web についてのパート 2 では、DSW のカスタマイズオプションについて説明します。  

カスタマイズには、ウィジェットのカスタマイズとダッシュボードパネルのカスタマイズの 2 種類があります。

開発者コミュニティ分析におけるダッシュボードのカスタマイズ例。

ウィジェットのカスタマイズ

DSW の各グラフウィジェットは、凡例、凡例要素、値、上位/全部の切り替えの要素によって調整することができます。

調整パネルは、グラフウィジェットの右上にあります。

ではその仕組みを見てみましょう。

凡例

小文字の「i」は凡例の切り替えを表します。 クリックすると凡例が表示/非表示になります。  また、凡例の特定の要素を表示/非表示にすることもできます。

上位行フィルタ

星ボタンは、上位/全部の切り替えボタンで、ピボットの最初の測定の上位 20 件フィルターを素早くオン/オフにできます。

ウィジェットに行数コントロールを導入すると、上位切り替えは上位/全部の値を自動的に使用するようになります。 当然、一般的な行カウント/列カウントコントロールを追加して、特殊な上位フィルタの動作を導入することも可能です。

値の切り替え

「V」は <s>vendetta</s> 値です。 クリックすると、行の値が表示/非表示になります。

ウィジェットのレイアウト

DSW は最初に DeepSee ダッシュボードリソースからウィジェットレイアウトを取得して、DSW 設定の列カウント(デフォルトは 12)とウィジェットの最小高さ(100)に合わせて拡大縮小されます。

ウィジェットのレイアウトは好みに合わせて移動して設定できます。 フィルタコントロールを配置する必要がある場合などには特に便利です(フィルタコントロールは、ウィジェットの代わりにダッシュボードにフィルタが配置される場合に自動的に表示されます)。

ダッシュボードパネルのカスタマイズ

DSW では、ダッシュボードパネルとタイル/カバーの外観をカスタマイズできます。

以下は、DeepSee ダッシュボードのクラシックパネルビューです。

DSW では、ダッシュボードパネルビューを 3 つの方法でカスタマイズできます(カスタマイズは設定で変更できます)。

Show images(画像を表示)をオンにすると、DeepSee の画像がダッシュボードカバーに表示されます。

すると、ダッシュボードパネルに画像が表示されます。

Show folders(フォルダを表示)を選択すると、同じダッシュボードパネルがフォルダで編成されます。

また、ダッシュボードカバーの色やサイズを調整し、対話型にすることができます。

ダッシュボードパネルの右下からタイルエディターを開きます。

ダッシュボード上の色、タイトル、画像を変更したり、ダッシュボードカバーの上にダッシュボードのウィジェットを配置したりできます。また、設定で、タイルの色テーマを確認できます。以下に例を示します。 これは、コミュニティ分析のコントラストテーマです。これは、同じダッシュボードパネルのメトロテーマです。

カスタマイズのデプロイ

デプロイについてはどうでしょうか。 設定やカスタマイズを開発からプロダクションソリューションに配布するには?

これを行うには、Settings(設定)メニューで現在のカスタマイズをエクスポートできます。

すべての設定が settings.json ファイルにエクスポートされるため、ターゲットプロダクションサーバーにデプロイすることができます。 DSW は、設定が含まれるファイルを /csp/dsw/configs/namespace.json ファイルで探します。

例: これは samples.json ファイルです。これを DSW インストールの /csp/dsw/config/samples.json に配置すれば、DSW がこのネームスペースのダッシュボードの設定を自動的に読み込むようになります。

尽きることのないカスタマイズ

デザイン、設定、調整などを行えるすべてのケースにおいて、カスタマイズが完成することは決してありません。 そのため、この分野において他の機能をご希望の場合は、課題をお送りいただくか、この機能を実装してプルリクエストをお送りください。

DSW の機能についてはさらに記事が追加されます。 パート 1 を読んで、今後の更新にご期待ください!

0
0 179
記事 Toshihiko Minamoto · 9月 15, 2023 5m read

DeepSee BI ソリューションのユーザーインターフェース(UI)を配布するにはいくつかのオプションがあります。 最も一般的には以下の手法があります。

  • ネイティブの DeepSee ダッシュボードを使用し、Zen で Web UI を取得して、Web アプリに配布する。
  • DeepSee REST API を使用して、独自の UI ウィジェットとダッシュボードを取得・構築する。

最初の手法はコーディングを行わずに比較的素早く BI ダッシュボードを構築できるためお勧めですが、事前設定のウィジェットライブラリに限られます。これを拡張することはできますが、大きな開発の手間がかかります。

2 つ目の手法には、任意の総合 js フレームワーク(D3,Highcharts など)を使用して DeepSee データを可視化する手段がありますが、ウィジェットとダッシュボードを独自にコーディングする必要があります。

今日は、上の 2 つを組み合わせて Angular ベースの DeepSee ダッシュボード用 Web UI を提供するもう 1 つの手法をご紹介します。DeepSee Web ライブラリです。

詳しい説明

DeepSee Web(DSW)は、DeepSee ダッシュボードを表示する Angular.js Web アプリで、Highcharts.js、OpenStreetMap、および一部の自作 js ウィジェットを使用する特定のネームスペースで使用できます。

仕組み

DSW は、MDX2JSON ライブラリを使用するネームスペースで使用できるダッシュボードとウィジェットのメタデータをリクエストします。 ダッシュボードを可視化するために、DSW はウィジェットとそのタイプのリストを読み取り、ほぼすべての DeepSee ウィジェットに実装されている js-widget アナログを使用します。 ダッシュボードにリストされる js ウィジェットをインスタンス化し、DeepSee REST API と MDX2JSON を使用してウィジェットのデータソースに従ってデータをリクエストし、JSON 形式でデータを取得して可視化を実行します。

そのメリットは?

DSW は以下の理由により優れています。

  • 標準のエディターを使用して DeepSee ダッシュボードを作成し、コーディングを一切行わずに Angular で UI を使用できます。
  • 任意の js ライブラリまたは自作ウィジェットで簡単にウィジェットのライブラリを拡張できます。
  • DSW カバーにスマートタイルを追加できます。
  • 地図にデータを表示できます。

モバイルデバイスは?

DSW は、モバイルブラウザ(Safari、Chrome)で非常によく動作し、スタンドアロンの DeepSight iOS アプリもあります。以下はそのスクリーンショットです。

いくらですか?

無料です。 最新リリースのダウンロード、課題の送信、フォークの作成、修正や新機能を含むプルリクエストを自由に行えます。

プロダクションでの使用に適していますか?

はい。 DSW は 2014 年にリリースされ、今日まで 60 回リリースされてきました。 DSW は数十社の DeepSee ソリューションで正常に使用されています。

たとえば、開発者コミュニティでも DSW を使用しています。

ただし、DSW ライセンス(MIT ライセンス)に従い、ご自身のリスクで使用していただきます。

 

サポートされていますか?

コミュニティによってサポートされています。 ぜひ課題を開き、フォークを作成して、修正と新機能が含まれるプルリクエストを送信してください。

主要貢献者は [@Anton Gnibeda​]、[@Nikita Savchenko]、[@Eduard Lebedyuk] です。 

インストール方法は?

簡単です。 まず、MDX2JSON をインストールします。 リリースから最新のインストーラーをダウンロードして、USER ネームスペースで installer.xml をインポート/コンパイルし、以下を実行します。

USER> D ##class(MDX2JSON.Installer).setup()

GitHub から最新のリリースがダウンロードされ、MDX2JSON データベースとネームスペースの作成と %All へのマッピングが行われ、/MDX2JSON Web アプリが作成されます。

すべてがうまくインストールされたことを確認するには、localhost:57772/MDX2JSON/Test を開きます。  すべてが正常である場合は、以下のように返されます。

{
"DefaultApp":"\/dsw",
"Mappings": {
"Mapped":["%SYS","MDX2JSON","SAMPLES","USER"
],
"Unmapped":["DOCBOOK"
]
},
"Parent":"18872dc518d8009bdc105425f541953d2c9d461e",
"ParentTS":"2017-05-26 16:20:17.307",
"Status":"OK",
"User":"",
"Version":2.2
}​

 

次に DSW をインストールします。 最新の release xml をダウンロードします。

それを USER ネームスペースにインポート/コンパイルします。 以下を実行してください。

USER> Do ##class(DSW.Installer).setup()

/dsw アプリが作成され、すべての js ファイルが /csp/dsw フォルダにインストールされます。

localhost:57772/dsw/index.html#!/?ns=SAMPLES を開き、動作することを確認してください。

既知の問題:

一部のシステムでは、CSP ゲートウェイで CSP 以外のファイルに UTF8 サポートを設定する必要があります。 ターミナルで以下を実行してオンにしてください。

USER> set ^%SYS("CSP","DefaultFileCharset")="utf-8"

 

いつインストールすべきですか?

今すぐインストールしましょう! )

続きにご期待ください!

0
0 177
記事 Toshihiko Minamoto · 1月 18, 2022 3m read

この短い記事では、マシンにPythonをセットアップしなくて済むように、dockerコンテナでYapeを実行する方法について説明します。

このシリーズの前回の記事からしばらく時間が経っているため、簡単に振り返ってみましょう。

まず、matplotlibで基本的なグラフを作成する方法について話しました。 そして、bokehを使った動的グラフについて紹介しました。 最後にパート3では、monlblデータを使ったヒートマップの生成について説明しました。

フィードバックをさまざまなチャンネルを通じて受け取りましたが、これらを実行するための環境をセットアップするのが困難であるという、共通したテーマが見られました。 そこで、それを少しでも簡単に行えるよう、Murrayと協力して、Murrayの優れたYapeツール用のDockerfileを作成してみることにしました。 GitHubページ

もちろん、これを行うには、マシンにdockerがインストールされている必要があります。

Dockerfile

公式のPythonイメージに基づく、どちらかと言えば単純なdocker定義:

FROM python:3

WORKDIR .

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ソース

Requirements.txtには、Yapeを起動して実行するために必要なパッケージが含まれています。

altgraph==0.10.2
py-dateutil==2.2
bdist-mpkg==0.5.0
certifi==2017.7.27.1
cffi==1.10.0
chardet==3.0.4
idna==2.5
bokeh==0.12.6
macholib==1.5.1
matplotlib==2.0.2
pandas==0.20.3
modulegraph==0.10.4
numpy==1.13.1
py2app==0.7.3
pycparser==2.18
pyparsing==2.0.1
python-dateutil==1.5
pytz==2013.7
requests==2.18.3
six==1.4.1
urllib3==1.22
zope.interface==4.1.1

ソース

イメージをビルドするには、GitHubリポジトリを確認してから、docker buildを実行します。

git clone https://github.com/murrayo/yape.git
docker build -t yape .

プルリクエスト がマージされるまでは、https://github.com/kazamatzuri/yape.gitを使用してください)

使用しているマシンやインターネット接続の速度にもよりますが、これには数分かかります。

その後、以下のようにして、pButtonsファイルでYapeを実行できます。

docker run -v `pwd`/in:/data  --rm --name yape-test yape  \
./extract_pButtons.py -o /data \ 
/data/pButtons.html

docker run -v `pwd`/in:/data  --rm --name yape-test yape  \ 
./graph_pButtons.py -o /data/charts /data

現在の作業ディレクトリでは

    /in

を使用しているため、それをコンテナの/dataにマッピングします。 そこからpButtons.htmlを取得し、グラフをそのディレクトリに出力します。

注意事項

スクリプトにパラメーターを追加する必要がありました。公式のYapeリポジトリにマージしているところです(プルリクエスト

0
0 127
記事 Toshihiko Minamoto · 1月 11, 2022 3m read

これまでに何度もコードカバレッジとコードのパフォーマンス最適化について説明してきたため、ほとんどの方はすでにSYS.MONLBLユーティリティについてご存知かと思います。 コードを視覚的に見る方が通常は、純粋な数値を見るよりもはるかに直感的に理解できます。これが、このシリーズの記事の大きなポイントです。 今回は、Pythonとそのツールから少し離れて、^%SYS.MONLBLレポートからヒートマップを生成する方法を探りたいと思います。

簡単に言うと、ヒートマップは特定の値を色で表現してデータの要約を得ることに特化した視覚化ツールです。 このケースでは、データはコード行であり、コード行に掛けられた時間が色にマッピングされます。

^%SYS.MONLBL

行ごとに監視するモニターの実行については、ドキュメントをご覧ください。 つまり、分析の完全な出力をCSVファイルとして操作します。 分析しようとしているコードのソースコードが実際にあれば、はるかに便利であるため、 kフラグ(ソースを保持)を使ってコードをコンパイルするようにしてください。

出力の準備

ターゲット出力として、準備されたhtmlファイルを使用することにします。 これには、非常に基本的なレイアウトと、最終的な色付けを行うための小さなJavaScript関数だけが含まれます。

<!doctype html>
<html class="no-js" lang="">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
        <link rel="apple-touch-icon" href="apple-touch-icon.png">

        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/4.2.0/normalize.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js"></script>
        <!--<link rel="stylesheet" href="css/main.css"> -->
            <style>

        table, th, td {
            width:"100%";
            border: 1px solid black;
            border-collapse: collapse;
        }
        pre {
            margin:1px;
        }
        th {
            text-align: left;
        }
    </style>

    <script>
    function rgba(r, g,b,a){
        r = Math.floor(r);
        g = Math.floor(g);
        b = Math.floor(b);
        return ["rgba(",r,",",g,",",b,",",a,")"].join("");
    }
    function colorize() {
        var rows=$("#data tr")
        var max=Math.max.apply(Math,rows.slice(1,rows.length).map(function(){ return this.childNodes[2].textContent}))
        for (i=1;i<rows.length;i++){
            var val=rows[i].childNodes[2].textContent;
            var c=(Math.pow(1-val/max,3))*255;
            var col=rgba(255,c,c,0.7);
            console.log(col);
            rows[i].style.backgroundColor=col;
        }
    }
    </script>
    </head>

    <body onload="colorize()">
        <!--[if lt IE 8]>
            <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
        <![endif]-->

        <script src="http://code.jquery.com/jquery-1.12.4.min.js"></script>
        <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.12.4.min.js"><\/script>')</script>

<table id="data">
<tr><th>Routine</th><th>Line</th><th>Total Time</th><th>Code</th></tr>
<!--output-->
</table>


    </body>
</html>

解析してまとめる

生成されたCSVから関連する情報を取得してテンプレートに入れるには、以下のスクリプトを使用します。

monlbl.sh

#!/bin/bash

cat $1|grep -vi totals| awk -F"," 'FNR>1 {out="<tr><td>"$1"</td>" "<td>" $2 "</td><td>" $54 "</td><td><pre>"; for(i=55;i<=NF;i++){out=out$i","}; out=substr(out, 1, length(out)-1) "</pre></td></tr>"; print out }'

gen-heatmap.sh

#!/bin/bash
./monlbl.sh $1 > /tmp/temp.data
sed -e '/<!--output-->/r/tmp/temp.data' template.html

上記を以下のようにして呼び出します。

./gen-heatmap.sh /tmp/report.csv > heatmap.html

最終的な出力 ヒートマップ

調整可能な要素

テンプレートに含まれる色付けの関数をよく見ると、時間には線形マッピングを使用していないことがわかります。

    function colorize() {
        var rows=$("#data tr")
        var max=Math.max.apply(Math,rows.slice(1,rows.length).map(function(){ return this.childNodes[2].textContent}))
        for (i=1;i<rows.length;i++){
            var val=rows[i].childNodes[2].textContent;
            var c=(Math.pow(1-val/max,3))*255;
            var col=rgba(255,c,c,0.7);
            console.log(col);
            rows[i].style.backgroundColor=col;
        }
    }

私がテストした例では、この方が非常にうまく機能することがわかりましたが、その度合いは条件によって異なる可能性があります。 明らかに、指数を増やしてより赤に押し込むこともできますし、その逆も可能です。

コード

関連ファイルはすべてこちらにあります。

0
0 172
記事 Toshihiko Minamoto · 12月 21, 2021 7m read

先週のディスカッションでは、1つのファイルのデータ入力に基づく単純なグラフを作成しました。 ご存知のように、解析して相関付けるデータファイルが複数あることがあります。 そこで今週は、perfmonデータを追加して読み込み、それを同じグラフにプロットする方法について学習しましょう。 生成したグラフをレポートやWebページで使用する可能性があるため、生成したグラフのエクスポート方法についても説明します。

Windowsのperfmonデータを読み込む

標準のpButtonsレポートから抽出されたperfmonデータは、少し独特なデータ形式です。 一見すると、かなり単純なCSVファイルで、 最初の行には列のヘッダーがあり、それ以降の行にはデータポイントが含まれています。 ただし、ここでの目的のために、値エントリーを囲む引用符をどうにかする必要があります。 標準的なアプローチを使用してファイルをPythonに解析すると、文字列オブジェクトの列ができてしまい、うまくグラフ化できません。

perfmonfile="../vis-part2/perfmon.txt"
perfmon=pd.read_csv(perfmonfile,
                    header=0,
                    index_col=0,
                    converters={0: parse_datetime
                    })

パート1で使用したmgstatファイルとは異なり、ヘッダー名は最初の行にあります。 最初の列にインデックスを定義しようと思います(こうすれば、前回のようにデータフレームのインデックスを再作成しなくて済むためです)。 最後に、実際にDateTimeを表すように最初の列を解析する必要があります。 そうでない場合、文字列インデックスになってしまうためです。 これを行うために、perfmonの日付を解析するヘルパー関数を定義します。

def parse_datetime(x):
    dt = datetime.strptime(x, '%m/%d/%Y %H:%M:%S.%f')

    return dt

これを実行したら、DataFrameでperfmonデータが得られました。
~~~python
<class 'pandas.core.frame.DataFrame'>
Index: 2104 entries, 01/03/2017 00:01:19.781 to 01/03/2017 17:32:51.957
Columns: 105 entries, \\WEBSERVER\Memory\Available MBytes to \\WEBSERVER\System\Processor Queue Length
dtypes: float64(1), int64(11), object(93)
memory usage: 1.7+ MB

大半の列は現在オブジェクトであることに注意してください。 これらの列を使用可能な形式に変換するために、to_numeric関数を使用します。 applyを使用して各列にこの関数を呼び出すことも可能ですが、それではインデックスがまた台無しになってしまいます。 そこで、データにその関数を適用しながら直接データをプロットすることにします。

プロット

この実行では、全CPUの合計特権時間をプロットすることに関心があります。 残念ながら、列番号は一定でなく、CPUとドライブの数によって異なります。 そのため、それがどの列かを調べて知る必要があります。 私の例では、91です。

perfmon.columns[91]

'\\\\WEBSERVER\\Processor(_Total)\\% Privileged Time'

前回と同じアプローチを使用して、Glorefs、Rdratio、および新しいPriviledged Timeでグラフを作成します。

plt.figure(num=None, figsize=(16,5), dpi=80, facecolor='w', edgecolor='k')
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)

par1 = host.twinx()
par2 = host.twinx()
offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",axes=par2,offset=(offset, 0))
par2.axis["right"].toggle(all=True)

host.set_xlabel("time")
host.set_ylabel("Glorefs")

par1.set_ylabel("Rdratio")
par2.set_ylabel("Privileged Time")
ws=30
p1,=host.plot(data.Glorefs,label="Glorefs")
p2,=par1.plot(data.Rdratio,label="Rdratio")
p3,=par2.plot(pd.to_numeric(perfmon[perfmon.columns[91]],errors='coerce'),label="PTime")

host.legend()

host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right"].label.set_color(p3.get_color())

plt.draw()
plt.show()

ここでto_numericを使用することができます。

p3,=par2.plot(pd.to_numeric(perfmon[perfmon.columns[91]],errors='coerce'),label="PTime")

グラフ

出力をリダイレクト

このノートブックは、データを一目で確認するのに非常に優れていますが、最終的には、スクリプトを非対話的に実行できるようにしたいため、グラフをイメージとして出力したいと思います。 スクリーンショットには、明らかに伴う手作業が多くなりすぎるため、puplot関数の*savefig()*を使用します。

*draw()show()の呼び出しをsavefig()*呼び出しに置き換えます。

#plt.draw()
#plt.show()
plt.savefig("ptime-out.png")

こうすると、現在の作業ディレクトリにpngが表示されます。

高度な出力

ちょっとした追加演習として、Bokehを見てみましょう。 Bokehがツールボックスに追加する多数の優れた機能の1つに、グラフをインタラクティブなHTMLファイルとして出力する機能があります。 インタラクティブとは、この場合、データをスクロールしてズームインできることを指します。 グラフ同士を関連付ける機能を追加すると、pButtonsデータ(またはその他のデータ)のインタラクティブなレンダリングを簡単に作成することができます。 これらは、あらゆる最新のブラウザで実行し、多数の人に簡単に配布できるため、特に便利です。

今のところは、2 つのグラフを出力に追加することを狙いとしています。 perfmonのGlorefsと特権時間を1つのページにまとめたいと思います。

そのためにはまず、Bokehをインポートする必要があります。

from bokeh.plotting import *

いくつかのプロパティとラベルを定義し、各プロットのサイズも決定します。 その後で、先に収集していたデータオブジェクトをプロットすれば、それで完了です。

コメントアウトされている*output_notebook()*は、Jupyterノートブックに直接出力をレンダリングすることに注意してください。 最終的にファイルを配布できるようにするため、output_fileを使用しています。

output_file("mgstat.html")
#output_notebook()
TOOLS="pan,box_zoom,reset,save"

left = figure(tools=TOOLS,x_axis_type='datetime',
    title="Glorefs",width=600, height=350,
   x_axis_label='time'
)
right=figure(tools=TOOLS,x_axis_type='datetime',
    title="PTime",width=600, height=350,
   x_axis_label='time',x_range=left.x_range
)
left.line(data.index,data.Glorefs,legend="Glorefs",line_width=1)
right.line(perfmon.index,pd.to_numeric(perfmon[perfmon.columns[91]],errors='coerce'),legend="privileged time",line_width=1)
p=gridplot([[left,right]])
show(p)

ここで重要なのは、2つのグラフの範囲の関連付けをx_range=left.x_rangeで行っているところです。 これにより、右側のウィンドウは左側のウィンドウでの選択/ズーム/移動によって(またはその逆で)更新されます。

TOOLSリストは、結果表示に含めたいツールのリストに過ぎません。 gridplotを使用することで、2つのグラフを隣り合わせに配置しています。

mgstat-bokeh

また、GitHubリポジトリでも、結果のHTMLを確認することができます。 GitHubから直接配信するには大きすぎるようなので、ダウンロードする必要があります。

まとめ

このセッションでは、さまざまなソースからデータを取得し、同じグラフにレンダリングする方法を探りました。 また、サンプリング頻度の異なるデータも扱っています(気づかなかったでしょう(^_-) )。 Bokehを使うと、グラフを配布可能なインタラクティブビューで簡単に作成できる強力なツールを得られます。 次のいくつかのセッションでは、csp.log、apache/iis アクセスログ、cconsole.logイベントなど、さらにプロットについて探りたいと思います。 Pythonで処理してほしいデータについて何か提案があれば、遠慮なくコメントに残してください。

あなたの体験をシェアしてください! これは読者との対話で成り立つ学習過程を意図したブログです!

リンク

この記事で使用したすべてのファイルは、こちらにあります。 また、ここで説明された一部のテクニックに基づく@murrayoの新しいeButtons抽出ツールもぜひご覧ください。 https://github.com/murrayo/yape

0
0 182
記事 Toshihiko Minamoto · 9月 23, 2021 10m read

この記事は、視覚化ツールと時系列データの分析を説明する連載の最初の記事です。 当然ながら、Caché製品ファミリーから収集できるパフォーマンス関連のデータを見ることに焦点を当てますが、 説明の途中で、他の内容についても解説していきます。 まずは、Pythonとそのエコシステムで提供されているライブラリ/ツールを探りましょう。

この連載は、Murrayが投稿したCachéのパフォーマンスと監視に関する優れた連載(こちらから参照)、より具体的にはこちらの記事と密接に関係しています。

免責事項1: 確認しているデータの解釈について話すつもりですが、それを詳しく話すと実際の目標から外れてしまう可能性があります。 そのため、Murrayの連載を先に読んで、主題の基本的な理解を得ておくことを強くお勧めします。

免責事項2: 収集したデータを視覚化するために使用できるツールは山ほどあります。 その多くは、mgstatなどから得たデータを直接処理するが、必要最低限の調整だけで処理することができます。 この記事は「このソリューションがベストですよ」という投稿ではまったくなく、 あくまでも、「データを操作する上で便利で効果的な方法を見つけたよ」という記事です。

免責事項3: データの視覚化と分析は、詳しく見るほど非常にやみつきになる、刺激的で楽しい分野です。 これに没頭して自由な時間を失ってしまう可能性があります。 でも、警告しましたからね!

では、前置きはこれくらいにして、早速内容を見ていきましょう。

前提条件

始める前に、次のツールとライブラリを用意してください。

  • Jupyterノートブック
  • Python (3)
  • 作業中に使用するさまざまなPythonライブラリ

Python(3): マシンに Python が必要です。 アーキテクチャごとにインストールの方法が様々です。 私はmacでhomebrewを使用しており、これを使って簡単にインストールできました。

brew install python3

Googleであなたのお気に入りのプラットフォームに使用できる手順を検索してください。

Jupyterノートブック: 厳密には必要ではありませんが、Jupyterノートブックがあれば、Pythonスクリプトでの作業が楽になります。 ブラウザウィンドウからインタラクティブにPythonスクリプトを実行して表示することができます。 また、スクリプトでの共同作業も可能です。 コードの実験と操作を非常に簡単に行えるようになるため、強くお勧めします。

pip3 install jupyter

(繰り返しになりますが、$search(検索)エンジンに尋ねてください)

Pythonライブラリ Pythonライブラリについては使用しながら説明します。 importステートメントでエラーが発生している場合は、まずは、ライブラリがインストールされていることを確認してください。

pip3 install matplotlib

はじめに

マシンにすべてがインストールされていれば、ライブラリから

jupyter notebook

を実行できるはずです。 このコードを実行すると自動的にブラウザウィンドウが開き、単純なUIが表示されます。 空のノートブック

早速メニューから新しいノートブックを作成し、最初のコードセルにインポートステートメントをいくつか追加します(新規 -> ノートブック -> Python3)。

ノートブックのインポート

import math
import pandas as pd
import mpl_toolkits.axisartist as AA
from mpl_toolkits.axes_grid1 import host_subplot
import matplotlib.pyplot as plt
from datetime import datetime
from matplotlib.dates import DateFormatter

インポートしているライブラリについて、いくつかのコメントがあります。

  • Pandas これは、Pythonプログラミング言語向けの高性能で使いやすいデータ構造とデータ解析ツールを提供するオープンソースのBSDラインセンスライブラリです。 これにより、ビッグデータセットを効率的に処理することができます。 pButtonsから取得するデータセットは、決して「ビッグデータ」ではありませんが、 一度にたくさんのデータを確認できるので安心です。 過去20年に渡って、24時間/2秒サンプリングでpButtonsを収集していたとしても、 それをグラフ化できるということです。
  • Matplotlib mataplotlibは、さまざまなハードコピー形式やプラットフォーム間でのインタラクティブ環境で出版グレードの図を生成するPythonの2Dグラフ作成ライブラリです。 この記事のメインのグラフ作成エンジンとしてとりあえず使用するのがこのライブラリです。

現在のコードセルの実行(ショートカット: Ctrl+Enter)(ショートカット一覧)でエラーが発生している場合は、上記のライブラリがインストールされていることを確認してください。

また、Untitledノートブックの名前も変更していることに気づくでしょう。名前を変更するには、タイトルをクリックしてください。

データの読み込み

基礎工事が終了したので、データをいくらか読み込むことにしましょう。 幸い、PandasにはCSVデータを簡単に読み込む方法があります。 都合よくcsv形式のmgstatデータのセットが手元にあるため、それを使うことにします。

mgstatfile = '/Users/kazamatzuri/work/proj/vis-articles/part1/mgstat.txt'
data = pd.read_csv(
    mgstatfile, 
    header=1,
    parse_dates=[[0,1]]
   )

read_csvコマンドを使用して、mgstatデータを直接DataFrameに読み取っています。 オプションに関する総合的な概要は、ドキュメント全文をご覧ください。 つまり、読み取るファイルを渡して、2行目(1行目は0です!)にヘッダー名が含まれていることを伝えているだけです。 mgstatは日付と時刻のフィールドを2つのフィールドに分割するため、parse_datesパラメーターでそれらのフィールドを組み合わせることも必要です。

data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 25635 entries, 0 to 25634
Data columns (total 37 columns):
Date_       Time        25635 non-null datetime64[ns]
  Glorefs               25635 non-null int64
 RemGrefs               25635 non-null int64
 GRratio                25635 non-null int64
  PhyRds                25635 non-null int64
 Rdratio                25635 non-null float64
 Gloupds                25635 non-null int64
 RemGupds               25635 non-null int64
 Rourefs                25635 non-null int64
 RemRrefs               25635 non-null int64
  RouLaS                25635 non-null int64
 RemRLaS                25635 non-null int64
  PhyWrs                25635 non-null int64
   WDQsz                25635 non-null int64
  WDtmpq                25635 non-null int64
 WDphase                25635 non-null int64
  WIJwri                25635 non-null int64
  RouCMs                25635 non-null int64
 Jrnwrts                25635 non-null int64
   GblSz                25635 non-null int64
 pGblNsz                25635 non-null int64
 pGblAsz                25635 non-null float64
   ObjSz                25635 non-null int64
 pObjNsz                25635 non-null int64
 pObjAsz                25635 non-null int64
   BDBSz                25635 non-null int64
 pBDBNsz                25635 non-null int64
 pBDBAsz                25635 non-null float64
  ActECP                25635 non-null int64
  Addblk                25635 non-null int64
 PrgBufL                25635 non-null int64
 PrgSrvR                25635 non-null int64
  BytSnt                25635 non-null int64
  BytRcd                25635 non-null int64
  WDpass                25635 non-null int64
  IJUcnt                25635 non-null int64
 IJULock                25635 non-null int64
dtypes: datetime64[ns](1), float64(3), int64(33)
memory usage: 7.2 MB

上記は、収集されたDataFrameの概要を示します。

データの操作

一部のフィールド名にはスペースが含まれており、「Date_ Time」は扱いにくいため、文字列を削除して最初の列の名前を変更します。

data.columns=data.columns.str.strip()
data=data.rename(columns={'Date_       Time':'DateTime'})

DataFrameはデフォルトでRangeIndexになります。 これはこのデータを確認するにはあまり役に立ちません。 より実用的なDateTime列を使用できるため、それをインデックスとして設定することにします。

data.index=data.DateTime

これで、最初のバージョンのプロットを作成できるようになりました。 最初に確認するものの1つは必ずGlorefsであるため、Glorefsを使用してみましょう。

plt.figure(num=None, figsize=(16,5), dpi=80, facecolor='w', edgecolor='k')
plt.xticks(rotation=70)
plt.plot(data.DateTime,data.Glorefs)
plt.show()

まず、ライブラリにグラフのサイズを指示しています。 また、x軸のラベルを少し回転させて、重なり合わないようにしています。 最後に、DateTimeとGlorefsのプロットを作成し、グラフを表示しています。 これで、次のようなグラフが出来上がります。

Glorefs

Glorefsをほかの列に置き換えるだけで、何が起きているのかを大まかに捉えることができます。

グラフの合成

複数のグラフをまとめて表示すると非常に便利な場合があります。 そのため、当然、複数のプロットを1つのグラフに描こうと考えるでしょう。 matplotlib だけでこれを行うのは非常に簡単です。

plt.plot(data.DateTime,data.Glorefs)
plt.plot(data.DateTime,data.PhyRds)
plt.show()

ところが、これでは前とほぼ同じグラフが作成されてしまいます。 問題はy軸です。 Glorefsは数百万に達するのに対し、PhyRdsはたいてい数百(から数千)であるため、PhyRdsはほぼ読み取れません。

これを解決するには、前にインポートしたaxisartistツールキットを使用する必要があります。

plt.gcf()
plt.figure(num=None, figsize=(16,5), dpi=80, facecolor='w', edgecolor='k')
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)

par1 = host.twinx()
par2 = host.twinx()
offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",axes=par2,offset=(offset, 0))
par2.axis["right"].toggle(all=True)

host.set_xlabel("time")
host.set_ylabel("Glorefs")
par1.set_ylabel("Rdratio")
par2.set_ylabel("PhyRds")

p1,=host.plot(data.Glorefs,label="Glorefs")
p2,=par1.plot(data.Rdratio,label="Rdratio")
p3,=par2.plot(data.PhyRds,label="PhyRds")

host.legend()

host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right"].label.set_color(p3.get_color())

plt.draw()
plt.show()

簡単に要約すると、2つのy軸をプロットに追加し、それぞれに異なる尺度を設定するということになります。 最初の例では暗黙的にサブプロットを使用しましたが、この場合は、直接それにアクセスして、軸とラベルを追加する必要があるため、 2つのラベルと色を設定しています。 凡例を追加して、様々なプロットに色を繋げたら、次のような画像になります。

合成

最後に

これでデータをプロットするための非常に強力なツールを手に入れました。 mgstatデータを読み込んでいくつかの基本的なプロットを作成する方法を探りました。 次のパートでは、ほかのグラフの出力形式を試し、さらに多くのデータを取得することにします。

コメントやご質問をお受け付けしております! あなたの体験をシェアしてください!

-Fab

追伸: これに使用するノートブックはこちらにあります。

0
0 155
記事 Toshihiko Minamoto · 11月 12, 2020 15m read

Prometheus時系列データの収集に適した監視システムです。

このシステムのインストールと初期構成は比較的簡単です。 このシステムにはデータ視覚化用の PromDashと呼ばれる画像サブシステムが組み込まれていますが、開発者は Grafana と呼ばれる無料のサードパーティ製品を使用することを推奨しています。 Prometheus は多くの要素(ハードウェア、コンテナ、さまざまな DBMS の構成要素)を監視できますが、この記事では Caché インスタンス(正確に言えば Ensemble インスタンスですが、メトリックは Caché からのものになります)の監視に注目したいと思います。 ご興味があれば、このまま読み進めてください。

非常に単純なケースでは、Prometheus と Caché は単一のマシン(Fedora Workstation 24 x86_64)上に存在します。 Caché のバージョンは以下のとおりです。

%SYS>write $zv
Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2016.1 (Build 656U) Fri Mar 11 2016 17:58:47 EST

インストールと構成

公式サイトから適切な Prometheus の配布パッケージをダウンロードし、/opt/prometheus フォルダーに保存してください。

アーカイブを解凍し、必要に応じてテンプレート構成ファイルを変更してから Prometheus を起動します。 Prometheus はデフォルトでコンソールにログを表示するため、ここではアクティビティレコードをログファイルに保存することにします。

Prometheus の起動

# pwd
/opt/prometheus
# ls
prometheus-1.4.1.linux-amd64.tar.gz
# tar -xzf prometheus-1.4.1.linux-amd64.tar.gz
# ls
prometheus-1.4.1.linux-amd64 prometheus-1.4.1.linux-amd64.tar.gz
# cd prometheus-1.4.1.linux-amd64/
# ls
console_libraries consoles LICENSE NOTICE prometheus prometheus.yml promtool
# cat prometheus.yml
global:
  scrape_interval: 15s # スクレイプ間隔を 15 秒に設定します。 デフォルトは 1 分ごとです。
scrape_configs:
  - job_name: 'isc_cache'
    metrics_path: '/metrics/cache'
    static_configs:
    - targets: ['localhost:57772']

# ./prometheus > /var/log/prometheus.log 2>&1 &
[1] 7117
# head /var/log/prometheus.log
time=«2017-01-01T09:01:11+02:00» level=info msg=«Starting prometheus (version=1.4.1, branch=master, revision=2a89e8733f240d3cd57a6520b52c36ac4744ce12)» source=«main.go:77»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Build context (go=go1.7.3, user=root@e685d23d8809, date=20161128-09:59:22)» source=«main.go:78»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Loading configuration file prometheus.yml» source=«main.go:250»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Loading series map and head chunks...» source=«storage.go:354»
time=«2017-01-01T09:01:11+02:00» level=info msg=«23 series loaded.» source=«storage.go:359»
time=«2017-01-01T09:01:11+02:00» level=info msg="Listening on :9090" source=«web.go:248»

prometheus.yml 構成ファイルは YAML 言語で記述されているため、タブ文字の使用は好ましくありません。したがって、スペースのみを使用する必要があります。 また、すでにお伝えしたとおり、メトリックは http://localhost:57772 からダウンロードされ、リクエストは /metrics/cache に送信されます(アプリケーション名は任意)。したがって、メトリック収集用の宛先アドレスは http://localhost:57772/metrics/cache になります。 「job = isc_cache」タグが各メトリックに追加されます。 大まかに言えば、タグは SQL の WHERE に相当するものです。 ここではタグを使用しませんが、複数のサーバーを使用する場合は役に立つでしょう。 例えばサーバー(またはインスタンス)の名前をタグに保存し、タグを使用してグラフ描画用のリクエストをパラメーター化することができます。 では、Prometheus が動作していることを確認しましょう(上記の出力では、9090 番ポートでリッスンしていることが分かります)。

Web インターフェイスが開きます。これは、Prometheus が機能していることを意味します。 ただし、Caché のメトリックはまだ表示されていません([Status] → [Targets] をクリックして確認しましょう)。

メトリックの準備

目標は、Prometheus が http://localhost:57772/metrics/cache適切なフォーマットでメトリックにアクセスできるようにすることです。 ここではその単純さを考慮し、Caché の REST 機能を使用します。 Prometheus は数値メトリックのみを「理解」するため、ここでは文字列メトリックをエクスポートしません。 後者を取得するには、SYS.Stats.Dashboard クラスの API を使用します。 このようなメトリックは、Caché 自体がシステムツールバーを表示する目的で使用されています。

同じ内容をターミナルで表示した例:

%SYS>set dashboard = ##class(SYS.Stats.Dashboard).Sample()  
 
%SYS>zwrite dashboard
dashboard= <OBJECT REFERENCE> [2@SYS.Stats.Dashboard]
+----------------- general information ---------------
|      oref value: 2
|      class name: SYS.Stats.Dashboard
| reference count: 2
+----------------- attribute values ------------------
|  ApplicationErrors = 0
|        CSPSessions = 2
|    CacheEfficiency = 2385.33
|      DatabaseSpace = "Normal"
|          DiskReads = 14942
|         DiskWrites = 99278
|       ECPAppServer = "OK"
|      ECPAppSrvRate = 0
|      ECPDataServer = "OK"
|     ECPDataSrvRate = 0
|            GloRefs = 272452605
|      GloRefsPerSec = "70.00"
|            GloSets = 42330792
|     JournalEntries = 16399816
|       JournalSpace = "Normal"
|      JournalStatus = "Normal"
|         LastBackup = "Mar 26 2017 09:58AM"
|     LicenseCurrent = 3
|  LicenseCurrentPct = 2
. . .

ここでは USER スペースがサンドボックスになります。 まず、REST アプリケーションの /metrics を作成しましょう。 非常に基本的な安全対策を行うため、ログインをパスワードで保護し、Web アプリケーションをリソースに関連付けます。このようなリソースを PromResource と呼びましょう。 リソースへの公開アクセスを無効にするため、次の内容を実行してください。

%SYS>write ##class(Security.Resources).Create("PromResource", "Resource for Metrics web page", "")
1

Web アプリの設定:

このリソースにアクセスできるユーザーも必要です。 このユーザーもデータベース(この場合は USER)から読み取り、そこへデータを保存できる必要があります。 また、別件ですがコードの後半部では %SYS スペースに切り替えるため、このユーザーには CACHESYS システムデータベースの読み取り権限が必要になります。 ここでは標準のスキームに従います。すなわち、これらの権限を持つ PromRole ロールを作成した後にこのロールに割り当てられた PromUser ユーザーを作成します。 パスワードには「Secret」を使いましょう。

%SYS>write ##class(Security.Roles).Create("PromRole","Role for PromResource","PromResource:U,%DB_USER:RW,%DB_CACHESYS:R"
1
%SYS>write ##class(Security.Users).Create("PromUser","PromRole","Secret")
1

Prometheus の構成では、この PromUser ユーザーを認証に使用します。 完了後はサーバープロセスに SIGNUP シグナルを送信し、構成を再読み込みします。

より安全な構成

# cat /opt/prometheus/prometheus-1.4.1.linux-amd64/prometheus.yml
global:
  scrape_interval: 15s # スクレイプ間隔を 15 秒に設定します。 デフォルトは 1 分ごとです。
 
scrape_configs:
  - job_name: 'isc_cache'
    metrics_path: '/metrics/cache'
    static_configs:
    - targets: ['localhost:57772']
    basic_auth:
      username: 'PromUser'
      password: 'Secret'

#
# kill -SIGHUP $(pgrep prometheus) # または kill -1 $(pgrep prometheus)

以上で Prometheus がメトリックを含む Web アプリケーションを使用するための認証をパスできるようになりました。

メトリックは、my.Metrics リクエスト処理クラスによって提供されます。以下にその実装を示します。

Class my.Metrics Extends %CSP.REST
{

Parameter ISCPREFIX = "isc_cache";

Parameter DASHPREFIX = {..#ISCPREFIX_"_dashboard"};

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/cache" Method="GET" Call="getMetrics"/>
</Routes>
}

/// 出力は Prometheus の出力フォーマットに従う必要があります。 ドキュメントは以下で確認できます。
/// https://prometheus.io/docs/instrumenting/exposition_formats/
/// 
/// このプロトコルは行指向です。 改行文字(\n)は行を区切ります。 
/// 最後の行は改行文字で終了する必要があります。 空の行は無視されます。
ClassMethod getMetrics() As %Status
{
    set nl = $c(10)
    do ..getDashboardSample(.dashboard)
    do ..getClassProperties(dashboard.%ClassName(1), .propList, .descrList)
    
    for i=1:1:$ll(propList) {
        set descr = $lg(descrList,i)
        set propertyName = $lg(propList,i)
        set propertyValue = $property(dashboard, propertyName)
        
        // Prometheusは時系列データベースをサポートします。 
        // そのため、空(バックアップメトリックなど)や非デジタルメトリックを 
        // 取得した場合はそれらを単に省略します。
        if ((propertyValue '= "") && ('$match(propertyValue, ".*[-A-Za-z ]+.*"))) {
            set metricsName = ..#DASHPREFIX_..camelCase2Underscore(propertyName)
            set metricsValue = propertyValue
            
            // 各メトリックの説明(ヘルプ)を記述します。
            // フォーマットはPrometheusが要求するものです。
            // 複数行の説明は1つの文字列に結合する必要があります。
            write "# HELP "_metricsName_" "_$replace(descr,nl," ")_nl
            write metricsName_" "_metricsValue_nl
        }
    }
    
    write nl
    quit $$$OK
}

ClassMethod getDashboardSample(Output dashboard)
{
    new $namespace
    set $namespace = "%SYS"
    set dashboard = ##class(SYS.Stats.Dashboard).Sample()
}

ClassMethod getClassProperties(className As %String, Output propList As %List, Output descrList As %List)
{
    new $namespace
    set $namespace = "%SYS"
    
    set propList = "", descrList = ""
    set properties = ##class(%Dictionary.ClassDefinition).%OpenId(className).Properties
    
    for i=1:1:properties.Count() {
        set property = properties.GetAt(i)
        set propList = propList_$lb(property.Name)
        set descrList = descrList_$lb(property.Description)
    }
}

/// キャメルケースのメトリック名を小文字のアンダースコア名に変換します
/// 例: 入力 = WriteDaemon、出力 = _write_daemon
ClassMethod camelCase2Underscore(metrics As %String) As %String
{
    set result = metrics
    set regexp = "([A-Z])"
    set matcher = ##class(%Regex.Matcher).%New(regexp, metrics)
    while (matcher.Locate()) {
        set result = matcher.ReplaceAll("_"_"$1")
    }
    
    // 小文字にします
    set result = $zcvt(result, "l")
    
    // _e_c_p (_c_s_p) を _ecp (_csp) にします
    set result = $replace(result, "_e_c_p", "_ecp")
    set result = $replace(result, "_c_s_p", "_csp")
    
    quit result
}
}

コンソールを使用して、私たちの作業が無駄ではなかったことを確認しましょう(curl がプログレスバーの表示を邪魔しないように --silent キーを追加しました)。

# curl --user PromUser:Secret --silent -XGET 'http://localhost:57772/metrics/cache' | head -20
# HELP isc_cache_dashboard_application_errors Number of application errors that have been logged.
isc_cache_dashboard_application_errors 0
# HELP isc_cache_dashboard_csp_sessions Most recent number of CSP sessions.
isc_cache_dashboard_csp_sessions 2
# HELP isc_cache_dashboard_cache_efficiency Most recently measured cache efficiency (Global references / (physical reads + writes))
isc_cache_dashboard_cache_efficiency 2378.11
# HELP isc_cache_dashboard_disk_reads Number of physical block read operations since system startup.
isc_cache_dashboard_disk_reads 15101
# HELP isc_cache_dashboard_disk_writes Number of physical block write operations since system startup
isc_cache_dashboard_disk_writes 106233
# HELP isc_cache_dashboard_ecp_app_srv_rate Most recently measured ECP application server traffic in bytes/second.
isc_cache_dashboard_ecp_app_srv_rate 0
# HELP isc_cache_dashboard_ecp_data_srv_rate Most recently measured ECP data server traffic in bytes/second.
isc_cache_dashboard_ecp_data_srv_rate 0
# HELP isc_cache_dashboard_glo_refs Number of Global references since system startup.
isc_cache_dashboard_glo_refs 288545263
# HELP isc_cache_dashboard_glo_refs_per_sec Most recently measured number of Global references per second.
isc_cache_dashboard_glo_refs_per_sec 273.00
# HELP isc_cache_dashboard_glo_sets Number of Global Sets and Kills since system startup.
isc_cache_dashboard_glo_sets 44584646

これで、Prometheus のインターフェースで同じ内容を確認できるようになりました。

以下は上記メトリックのリストです。

これらのメトリックの Prometheus での表示内容については詳述しません。 必要なメトリックを選択して「Execute」ボタンをクリックしてください。 「Graph」タブを選択すると、グラフが表示されます(キャッシュの効率が表示されます)。

メトリックの視覚化

メトリックを視覚化するため、Grafana をインストールしましょう。 この記事では、tarball からインストールすることにしました。 ただし、パッケージからコンテナまで、他のインストール方法もあります。 次の手順を実行してみましょう(/opt/grafana フォルダーを作成し、そこに切り替えた後)。

とりあえず設定は変更せずにそのままにしておきましょう。 最後のステップでは、Grafana をバックグラウンドモードで起動します。 Prometheus の場合と同じように、Grafana のログをファイルに保存します。

# ./bin/grafana-server > /var/log/grafana.log 2>&1 &

デフォルトでは、3000 番ポートで Grafana の Web インターフェースにアクセスできます。 ログイン/パスワードは、admin/admin です。

詳細な Prometheus と Grafana の連携手順については、こちらを参照してください。 簡単に言えば、Prometheus タイプの新しいデータソースを追加する必要があります。 また、次のように direct/proxy アクセスのオプションを選択してください。

完了後、必要なパネルを含むダッシュボードを追加する必要があります。 ダッシュボードのテストサンプルは、メトリック収集クラスのコードと共に公開されています。 ダッシュボードは Grafana に簡単にインポートできます([Dashboards] → [Import])。

インポート後、次のようになります。

ダッシュボードを保存します。

時間範囲と更新間隔は右上で選択できます。

監視種類の例

グローバルへの呼び出しの監視をテストしてみましょう。

USER>for i=1:1:1000000 {set ^prometheus(i) = i}
USER>kill ^prometheus

以下のグラフでは、1秒あたりのグローバルへの参照数が増加してキャッシュ効率が低下していることが分かります(^Prometheus グローバルがまだキャッシュされていない)。

ライセンスの使用状況を確認しましょう。 そのためには、次のように PromTest.csp というプリミティブな CSP ページを USER ネームスペースに作成しましょう。


監視は正常に機能しています!

そして、何度もアクセスしてください(/csp/user アプリケーションがパスワード保護されていないことを想定しています)。

# ab -n77 http://localhost:57772/csp/user/PromTest.csp

ライセンスの使用状況について、次の図が表示されます。

まとめ

ご覧のとおり、監視機能の実装はまったく難しくありません。 いくつかの初期手順を実行しただけでも、ライセンスの使用状況、グローバルキャッシュの効率、アプリケーションエラーなど、システムの動作に関する重要な情報を取得できます。 このチュートリアルでは SYS.Stats.Dashboard を使用しましたが、SYS / %SYSTEM / %SYS パッケージの他のクラスも注目に値します。 また、特定タイプのドキュメントの数など、独自アプリケーションのカスタムメトリックを提供する独自のクラスを作成することもできます。 いくつかの有用なメトリックは、最終的に Grafana 用の個別テンプレートにコンパイルされます。

今後の予定

本件についてより詳細な情報が必要な場合は、このテーマを詳しく説明するつもりです。 以下に私の予定を記しておきます。

  1. ログデーモンのメトリックを含む Grafana テンプレートの準備について。 ^mgstat と同等の、少なくともそのメトリックに対応した何らかのグラフィカルツールを作成するのが望ましいと考えています。

  2.   <li>
        <p>
          Web アプリケーションのパスワード保護は優れていますが、証明書を使用できる可能性を確認するのが望ましいと考えています。
        </p>
      </li>
      
      <li>
        <p>
          Prometheus、Grafana、および Prometheus を Docker コンテナとしてエクスポートするツールの使用について。
        </p>
      </li>
      
      <li>
        <p>
          新しい Caché インスタンスを Prometheus の監視リストに自動追加するための検出サービスの使用について。 また、Grafana とそのテンプレートの利便性を(実際に)説明したいと考えています。 これは、選択した特定のサーバーのメトリックがすべて同じダッシュボードに表示される動的なパネルのようなものです。
        </p>
      </li>
      
      <li>
        <p>
          Prometheus Alertmanager について。
        </p>
      </li>
      
      <li>
        <p>
          データの保存期間に関連する Prometheus の構成設定、および多数のメトリックと短い統計収集間隔を持つシステムに考えられる最適化について。
        </p>
      </li>
      
      <li>
        <p>
          途中で発生するさまざまで微妙な差異について。
        </p>
      </li>
    </ol>
    

    リンク

    この記事を準備中にいくつかの有益なサイトにアクセスし、次のようなたくさんの動画を視聴しました。

  <li>
    <p>
      <a href="http://grafana.org/">Grafana プロジェクトの Web サイト</a>
    </p>
  </li>
  
  <li>
    <p>
      <a href="https://www.robustperception.io/blog/">Brian Brazil という Prometheus 開発者のブログ</a>
    </p>
  </li>
  
  <li>
    <p>
      <a href="https://www.digitalocean.com/community/tutorials/how-to-use-prometheus-to-monitor-your-ubuntu-14-04-server">DigitalOcean のチュートリアル</a>
    </p>
  </li>
  
  <li>
    <p>
      <a href="https://www.youtube.com/channel/UCtiFWOeRSTP3M6QUnTEKwpw">Robust Perception の動画数点</a>
    </p>
  </li>
  
  <li>
    <p>
      <a href="https://www.youtube.com/channel/UC4pLFely0-Odea4B2NL1nWA/videos">Prometheus を対象とする多数のカンファレンス動画</a>
    </p>
  </li>
</ul>

最後までお読みいただき、ありがとうございました!

3
0 628
記事 Toshihiko Minamoto · 11月 11, 2020 22m read

こんにちは! この記事は「Prometheus で InterSystems Caché を監視する」の続きになります。 ここでは ^mgstat ツールの動作結果を視覚化する方法を見ていきます。 このツールを使用すると、Caché のパフォーマンス統計、具体的なグローバルとルーチンの呼び出し数(ローカルおよびECP 経由)、書き込みデーモンのキュー長、ディスクに保存されるブロックと読み取られるブロックの数、ECP トラフィックの量などを取得できます。 ^mgstat は(対話的に、またはジョブによって)単独で起動したり、別のパフォーマンス測定ツールである ^pButtons と並行して起動したりできます。

ここでは 2 つのパートに分けて説明したいと思います。最初のパートでは ^mgstat によって収集された統計を図示し、2 番目のパートではこの統計を正確に収集する方法を集中して取り上げます。 手短に言えば、ここでは $zu関数 を使用しています。 ただし、SYS.Stats パッケージのクラス経由で収集できる大部分のパラメーターに対応したオブジェクトインターフェースがあります。 ^mgstat に表示されるのは、収集できるパラメーターのほんの一部です。 その後、Grafana ダッシュボードですべてのパラメーターを表示してみましょう。 今回は ^mgstat によって提供されるパラメーターのみを使用します。 また、Docker コンテナについても説明のために少しだけ取り上げます。

Docker のインストール

最初のパートでは tarball から Prometheus と Grafana をインストールする方法を説明しています。 ここでは Docker の機能を使用して監視サーバーを起動する方法を説明します。 以下はデモ用のホストマシンです。

# uname -r
4.8.16-200.fc24.x86_64
# cat /etc/fedora-release
Fedora release 24 (Twenty Four)

さらに 2 台の仮想マシン(192.168.42.131 と 192.168.42.132)が VMWare Workstation Pro 12.0 環境で使用され、どちらも Caché がインストールされています。 これらのマシンが監視対象になります。 バージョンは次のとおりです。

# uname -r
3.10.0-327.el7.x86_64
# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.2 (Maipo)

USER>write $zversion
Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2016.2 (Build 721U) Wed Aug 17 2016 20:19:48 EDT

ホストマシンに Docker をインストールして起動しましょう。

# dnf install -y docker
# systemctl start docker
# systemctl status docker
● docker.service — Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
Active: active (running) since Wed 2017-06-21 15:08:28 EEST; 3 days ago
...

Docker コンテナで Prometheus を起動する

最新の Prometheus イメージをロードしましょう。

# docker pull docker.io/prom/prometheus

Docker ファイルを参照すると、イメージが /etc/prometheus/prometheus.yml ファイルから構成を読み取り、収集された統計が /prometheusフォルダーに保存されていることがわかります。


CMD [ "-config.file=/etc/prometheus/prometheus.yml", \
"-storage.local.path=/prometheus", \
...

Docker コンテナで Prometheus を起動する際に、ホストマシンから構成ファイルとメトリックデータベースをロードするようにしましょう。 こうすることで、コンテナの再起動を「乗り切る」ことができます。 次に、ホストマシン上に Prometheus 用のフォルダーを作成しましょう。

# mkdir -p /opt/prometheus/data /opt/prometheus/etc

そして、次のような Prometheus の構成ファイルを作成しましょう。

cat /opt/prometheus/etc/prometheus.yml
global:
  scrape_interval: 10s
scrape_configs:
  - job_name: 'isc_cache'
    metrics_path: '/mgstat/5' # Tail 5 (sec) it's a diff time for ^mgstat. Should be less than scrape interval.
    static_configs:
    - targets: ['192.168.42.131:57772','192.168.42.132:57772']
    basic_auth:
      username: 'PromUser'
      password: 'Secret'

これで、Prometheus を含むコンテナを起動できます。

# docker run -d --name prometheus \
--hostname prometheus -p 9090:9090 \
-v /opt/prometheus/etc/prometheus.yml:/etc/prometheus/prometheus.yml \
-v /opt/prometheus/data/:/prometheus \
docker.io/prom/prometheus

正常に起動したかどうかを確認してください。

# docker ps --format "{{.ID}}: {{.Command}} {{.Status}} {{.Names}}"
d3a1db5dec1a: "/bin/prometheus -con" Up 5 minutes prometheus

Docker コンテナで Grafana を起動する

まずは最新のイメージをダウンロードしましょう。

# docker pull docker.io/grafana/grafana

次に、Grafana データベース(デフォルトでは SQLite)をホストマシンに保存するように指定して起動します。 また、Prometheus を含むコンテナへのリンクを作成し、Grafana を含むコンテナからそのコンテナにリンクできるようにします。

# mkdir -p /opt/grafana/db
# docker run -d --name grafana \
--hostname grafana -p 3000:3000 \
--link prometheus \
-v /opt/grafana/db:/var/lib/grafana \
docker.io/grafana/grafana
# docker ps --format "{{.ID}}: {{.Command}} {{.Status}} {{.Names}}"
fe6941ce3d15: "/run.sh" Up 3 seconds grafana
d3a1db5dec1a: "/bin/prometheus -con" Up 14 minutes prometheus

Docker-compose を使用する

両方のコンテナは別々に起動されます。 Docker-compose を使用すると、まとめて複数のコンテナを起動できるので便利です。 このツールをインストールし、現在の 2 つのコンテナを一時停止しましょう。その後、Docker-compose 経由で再起動するように再構成し、これらのコンテナをもう一度起動します。

これを cli で書くと次のようになります。

dnf install -y docker-compose
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
mkdir /opt/docker-compose
cat /opt/docker-compose/docker-compose.yml
version: '2'
services:
  prometheus: 
    image: docker.io/prom/prometheus
    container_name: prometheus
    hostname: prometheus
    ports: 
      - 9090:9090
    volumes:
      - /opt/prometheus/etc/prometheus.yml:/etc/prometheus/prometheus.yml
      - /opt/prometheus/data/:/prometheus
  grafana: 
    image: docker.io/grafana/grafana
    container_name: grafana
    hostname: grafana
    ports: 
      - 3000:3000
    volumes:
      - /opt/grafana/db:/var/lib/grafana
docker-compose -f /opt/docker-compose/docker-compose.yml up -d
# # 両方のコンテナを次のコマンドを使用して無効化および削除できます。 
# # docker-compose -f /opt/docker-compose/docker-compose.yml down
docker ps --format "{{.ID}}: {{.Command}} {{.Status}} {{.Names}}"
620e3cb4a5c3: "/run.sh" Up 11 seconds grafana
e63416e6c247: "/bin/prometheus -con" Up 12 seconds prometheus

インストール後の手順

Grafana を初めて起動した後は、Web インターフェイスの管理者パスワードを変更し(デフォルトでは、ログインとパスワードの組み合わせは admin/admin です)、Prometheus をデータソースとして追加する必要があります。 この手順は Web インターフェイスから実施できますが、Grafana SQLite データベース(デフォルトの位置は /opt/grafana/db/grafana.db)を直接編集するか、REST リクエストを使用することによっても実施できます。

さらにもう 1 つの方法をご紹介します。次をご覧ください。

curl -XPUT "admin:admin@localhost:3000/api/user/password" \
-H "Content-Type:application/json" \
-d '{"oldPassword":"admin","newPassword":"TopSecret","confirmNew":"TopSecret"}'

パスワードが正常に変更された場合は次の応答が返ってきます。
{"message":"User password changed"}
次のような応答が返ってくる場合があります:
curl: (56) Recv failure: Connection reset by peer
これは、Grafana サーバーがまだ起動しておらず、前述のコマンドを再度実行する前に少し待機する必要があることを意味しています。 例えば、次のように待機できます。
until curl -sf admin:admin@localhost:3000 > /dev/null; do sleep 1; echo "Grafana is not started yet";done; echo "Grafana is started"
パスワードを正常に変更したら、Prometheus のデータソースを追加してください。
curl -XPOST "admin:TopSecret@localhost:3000/api/datasources" \
-H "Content-Type:application/json" \
-d '{"name":"Prometheus","type":"prometheus","url":"http://prometheus:9090","access":"proxy"}'

データソースが正常に追加されると、次の応答が返ってきます。
{"id":1,"message":"Datasource added","name":"Prometheus"}

^mgstat に相当するものを作成する

^mgstat は対話モードで出力をファイルとターミナルに保存します。 ここではファイルへの出力は取り上げません。 このため、Studio を使用して USER スペースに ^mgstat をオブジェクト指向で実装した my.Metrics というクラスを作成してコンパイルします。

/// このクラスは ^mgstat ルーチンをオブジェクト指向で実装しています。
/// 前回とは異なり、Caché のバージョンチェックはスキップされます。
/// seizes を監視したい場合はパラメーター ISSEIZEGATHERED を 1 に設定する必要があります。
/// ^mgstat ルーチンとは異なり、Seizes メトリックは(パーセンテージではなく)差分として表示されます。
/// 一部の $zutil 関数についてはよく分かりませんが、^mgstat で使用されているので残しておきます。
Class my.Metrics Extends %RegisteredObject
{

/// メトリックの接頭辞
Parameter PREFIX = "isc_cache_mgstat_";

/// Prometheus のメトリックは改行で区切る必要があります。
Parameter NL As COSEXPRESSION = "$c(10)";

/// 不明なパラメーターです -) ^mgstat.int と同じものを使用しています。
Parameter MAXVALUE = 1000000000;

/// 2**64 - 10 です。 なぜマイナス 10 なのでしょうか? 分かりません -) ^mgstat.int と同じものを使用しています。
Parameter MAXVALGLO = 18446744073709551610;

/// 監視対象にするリソースです。 このリストは変更できます。
Parameter SEIZENAMES = "Global,ObjClass,Per-BDB";

/// デフォルト値は $zutil(69,74) です。 "1" を設定すると seize 統計の収集を開始できます。
Parameter ISSEIZEGATHERED = 0;

Parameter MAXECPCONN As COSEXPRESSION = "$system.ECP.MaxClientConnections()";

/// グローバルバッファタイプの数(8K、16K など)
Parameter NUMBUFF As COSEXPRESSION = "$zutil(190, 2)";

/// メモリオフセット(用途不明 )
Parameter WDWCHECK As COSEXPRESSION = "$zutil(40, 2, 146)";

/// 書き込みデーモンフェーズ用のメモリオフセット
Parameter WDPHASEOFFSET As COSEXPRESSION = "$zutil(40, 2, 145)";

/// ジャーナル用のオフセット
Parameter JOURNALBASE As COSEXPRESSION = "$zutil(40, 2, 94)";

ClassMethod getSamples(delay As %Integer = 2) As %Status
{
    set sc = $$$OK
    try {
        set sc = ..gather(.oldValues)
        hang delay
        set sc = ..gather(.newValues)
        set sc = ..diff(delay, .oldValues, .newValues, .displayValues)
        set sc = ..output(.displayValues)
    } catch e {
        write "Error: "_e.Name_"_"_e.Location, ..#NL
    }
    quit sc
}

ClassMethod gather(Output values) As %Status
{
    set sc = $$$OK
    
    // グローバルの統計を取得
    set sc = ..getGlobalStat(.values)
    
    // 書き込みデーモンの統計を取得
    set sc = ..getWDStat(.values)
    
    // ジャーナルの書き込みを取得
    set values("journal_writes") = ..getJournalWrites()
    
    // seize の統計を取得
    set sc = ..getSeizeStat(.values)
    
    // ECP の統計を取得
    set sc = ..getECPStat(.values)
    
    quit sc
}

ClassMethod diff(delay As %Integer = 2, ByRef oldValues, ByRef newValues, Output displayValues) As %Status
{
    set sc = $$$OK
    
    // グローバルのメトリックを処理
    set sc = ..loopGlobal("global", .oldValues, .newValues, delay, 1, .displayValues)
    
    set displayValues("read_ratio") = $select(
        displayValues("physical_reads") = 0: 0,
        1: $number(displayValues("logical_block_requests") / displayValues("physical_reads"),2)
    )
    set displayValues("global_remote_ratio") = $select(
        displayValues("remote_global_refs") = 0: 0,
        1: $number(displayValues("global_refs") / displayValues("remote_global_refs"),2)
    )
    
    // 書き込みデーモンのメトリックを処理(秒単位ではない)
    set sc = ..loopGlobal("wd", .oldValues, .newValues, delay, 0, .displayValues)
    
    // ジャーナルの書き込みを処理
    set displayValues("journal_writes") = ..getDiff(oldValues("journal_writes"), newValues("journal_writes"), delay)
    
    // seize メトリックの処理
    set sc = ..loopGlobal("seize", .oldValues, .newValues, delay, 1, .displayValues)
    
    // ECP クライアントメトリックの処理
    set sc = ..loopGlobal("ecp", .oldValues, .newValues, delay, 1, .displayValues)
    set displayValues("act_ecp") = newValues("act_ecp")
    
    quit sc
}

ClassMethod getDiff(oldValue As %Integer, newValue As %Integer, delay As %Integer = 2) As %Integer
{
    if (newValue < oldValue) {
        set diff = (..#MAXVALGLO - oldValue + newValue) \ delay
        if (diff > ..#MAXVALUE) set diff = newValue \ delay
    } else {
        set diff = (newValue - oldValue) \ delay
    }
    quit diff
}

ClassMethod loopGlobal(subscript As %String, ByRef oldValues, ByRef newValues, delay As %Integer = 2, perSecond As %Boolean = 1, Output displayValues) As %Status
{
    set sc = $$$OK
    
    set i = ""
    for {
        set i = $order(newValues(subscript, i)) 
        quit:(i = "")
        if (perSecond = 1) {
            set displayValues(i) = ..getDiff(oldValues(subscript, i), newValues(subscript, i), delay)
        } else {
            set displayValues(i) = newValues(subscript, i)
        }
    }
    
    quit sc
}

ClassMethod output(ByRef displayValues) As %Status
{
    set sc = $$$OK
    set i = ""
    for {
        set i = $order(displayValues(i))
        quit:(i = "")
        write ..#PREFIX_i," ", displayValues(i),..#NL
    }
    write ..#NL
    quit sc
}

ClassMethod getGlobalStat(ByRef values) As %Status
{
    set sc = $$$OK
    
    set gloStatDesc = "routine_refs,remote_routine_refs,routine_loads_and_saves,"_
        "remote_routine_loads_and_saves,global_refs,remote_global_refs,"_
        "logical_block_requests,physical_reads,physical_writes,"_
        "global_updates,remote_global_updates,routine_commands,"_
        "wij_writes,routine_cache_misses,object_cache_hit,"_
        "object_cache_miss,object_cache_load,object_references_newed,"_
        "object_references_del,process_private_global_refs,process_private_global_updates"
        
    set gloStat = $zutil(190, 6, 1)
    
    for i = 1:1:$length(gloStat, ",") {
        set values("global", $piece(gloStatDesc, ",", i)) = $piece(gloStat, ",", i)
    }

    quit sc
}

ClassMethod getWDStat(ByRef values) As %Status
{
    set sc = $$$OK
    
    set tempWdQueue = 0 
    for b = 1:1:..#NUMBUFF { 
        set tempWdQueue = tempWdQueue + $piece($zutil(190, 2, b), ",", 10) 
    }
    
    set wdInfo = $zutil(190, 13)
    set wdPass = $piece(wdInfo, ",")
    set wdQueueSize = $piece(wdInfo, ",", 2)
    set tempWdQueue = tempWdQueue - wdQueueSize 
    if (tempWdQueue < 0) set tempWdQueue = 0
    
    set misc = $zutil(190, 4)
    set ijuLock = $piece(misc, ",", 4)
    set ijuCount = $piece(misc, ",", 5)
    
    set wdPhase = 0 
    if (($view(..#WDWCHECK, -2, 4)) && (..#WDPHASEOFFSET)) {
        set wdPhase = $view(..#WDPHASEOFFSET, -2, 4)
    }
    
    set wdStatDesc = "write_daemon_queue_size,write_daemon_temp_queue,"_
        "write_daemon_pass,write_daemon_phase,iju_lock,iju_count"
    
    set wdStat = wdQueueSize_","_tempWdQueue_","_wdPass_","_wdPhase_","_ijuLock_","_ijuCount
    
    for i = 1:1:$length(wdStat, ",") {
        set values("wd", $piece(wdStatDesc, ",", i)) = $piece(wdStat, ",", i)
    }
    
    quit sc
}

ClassMethod getJournalWrites() As %String
{
    quit $view(..#JOURNALBASE, -2, 4)
}

ClassMethod getSeizeStat(ByRef values) As %Status
{
    set sc = $$$OK
    
    set seizeStat = "", seizeStatDescList = ""
    set selectedNames = ..#SEIZENAMES
    
    set seizeNumbers = ..getSeizeNumbers(selectedNames)  // seize statistics
    set isSeizeGatherEnabled = ..#ISSEIZEGATHERED
    if (seizeNumbers = "") { 
        set SeizeCount = 0 
    } else { 
        set SeizeCount = isSeizeGatherEnabled * $length(seizeNumbers, ",") 
    }
    
    for i = 1:1:SeizeCount { 
        set resource = $piece(seizeNumbers, ",", i)
        set resourceName = ..getSeizeLowerCaseName($piece(selectedNames, ",", i))
        set resourceStat = $zutil(162, 3, resource)
        set seizeStat = seizeStat_$listbuild($piece(resourceStat, ","))
        set seizeStat = seizeStat_$listbuild($piece(resourceStat, ",", 2))
        set seizeStat = seizeStat_$listbuild($piece(resourceStat, ",", 3))
        set seizeStatDescList = seizeStatDescList_$listbuild(
            resourceName_"_seizes", resourceName_"_n_seizes", resourceName_"_a_seizes"
        )
    }
    set seizeStatDesc = $listtostring(seizeStatDescList, ",")
    
    set seizeStat = $listtostring(seizeStat, ",")
    
    if (seizeStat '= "") {
        for k = 1:1:$length(seizeStat, ",") {
            set values("seize", $piece(seizeStatDesc, ",", k)) = $piece(seizeStat, ",", k)
        }
    }
    
    quit sc
}

ClassMethod getSeizeNumbers(selectedNames As %String) As %String
{
    /// USER>write $zu(162,0)
    // Pid,Routine,Lock,Global,Dirset,SatMap,Journal,Stat,GfileTab,Misc,LockDev,ObjClass...
    set allSeizeNames = $zutil(162,0)_"," //すべてのリソース名を返す
    
    set seizeNumbers = ""
    for i = 1:1:$length(selectedNames, ",") {
        set resourceName = $piece(selectedNames,",",i)
        continue:(resourceName = "")||(resourceName = "Unused")
        set resourceNumber = $length($extract(allSeizeNames, 1, $find(allSeizeNames, resourceName)), ",") - 1
        continue:(resourceNumber = 0)
        if (seizeNumbers = "") {
            set seizeNumbers = resourceNumber
        } else {
            set seizeNumbers = seizeNumbers_","_resourceNumber
        }
    }
    quit seizeNumbers
}

ClassMethod getSeizeLowerCaseName(seizeName As %String) As %String
{
    quit $tr($zcvt(seizeName, "l"), "-", "_")
}

ClassMethod getECPStat(ByRef values) As %Status
{
    set sc = $$$OK
    
    set ecpStat = ""
    
    if (..#MAXECPCONN '= 0) {
        set fullECPStat = $piece($system.ECP.GetProperty("ClientStats"), ",", 1, 21)
        set activeEcpConn = $system.ECP.NumClientConnections()
        set addBlocks = $piece(fullECPStat, ",", 2)
        set purgeBuffersByLocal = $piece(fullECPStat, ",", 6)
        set purgeBuffersByRemote = $piece(fullECPStat, ",", 7)
        set bytesSent = $piece(fullECPStat, ",", 19)
        set bytesReceived = $piece(fullECPStat, ",", 20)
    }
    set ecpStatDesc = "add_blocks,purge_buffers_local,"_
        "purge_server_remote,bytes_sent,bytes_received"
    
    set ecpStat = addBlocks_","_purgeBuffersByLocal_","_
        purgeBuffersByRemote_","_bytesSent_","_bytesReceived
        
    if (ecpStat '= "") {
        for l = 1:1:$length(ecpStat, ",") {
            set values("ecp", $piece(ecpStatDesc, ",", l)) = $piece(ecpStat, ",", l)
        }
        set values("act_ecp") = activeEcpConn
    }
    
    quit sc
}

}

REST 経由で my.Metrics を呼び出すため、USER スペースにラッパークラスを作成しましょう。

Class my.Mgstat Extends %CSP.REST
{

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{



}

ClassMethod getMgstat(delay As %Integer = 2) As %Status
{
    // デフォルトでは 2 秒間隔で平均値を取得します
    quit ##class(my.Metrics).getSamples(delay)
}

}

リソース、ユーザー、Web アプリケーションを作成する

メトリックを提供するクラスが完成し、RESTful Web アプリケーションを作成できるようになりました。 最初の記事と同様に、この Web アプリケーションにリソースを割り当て、そのリソースを使用して Prometheus がメトリックを収集するユーザーを作成します。 作成したら、特定のデータベースにユーザー権限を付与しましょう。 最初の記事とは異なり、CACHESYS データベースに書き込むための権限( <UNDEFINED>loop+1^mymgstat *gmethod" エラーを回避するため)を追加し、%Admin_Manage リソースを使用できるようにしました( <PROTECT>gather+10^mymgstat *GetProperty,%SYSTEM.ECP" エラーを回避するため)。 192.168.42.131 と 192.168.42.132 の両方の仮想サーバーでこれらの手順を繰り返しましょう。 ただし、その前に作成した my.Metrics クラスと my.Mgstat クラスのコードを両方のサーバーの USER スペースにアップロードします(コードは GitHub で取得できます)。

具体的にはそれぞれの仮想サーバーで次の手順を実行します。

cd /tmp
wget https://raw.githubusercontent.com/myardyas/prometheus/master/mgstat/cos/udl/Metrics.cls
wget https://raw.githubusercontent.com/myardyas/prometheus/master/mgstat/cos/udl/Mgstat.cls
#
# # サーバーがインターネットに接続されていない場合はプログラムとクラスをローカル環境でコピーし、 scp を使用してください。
#
csession -U user
USER>do $system.OBJ.Load("/tmp/Metrics.cls*/tmp/Mgstat.cls","ck")
USER>zn "%sys"
%SYS>write ##class(Security.Resources).Create("PromResource","Resource for Metrics web page","") 
1
%SYS>write ##class(Security.Roles).Create("PromRole","Role for PromResource","PromResource:U,%Admin_Manage:U,%DB_USER:RW,%DB_CACHESYS:RW")
1
%SYS>write ##class(Security.Users).Create("PromUser","PromRole","Secret")
1
%SYS>set properties("NameSpace") = "USER"
%SYS>set properties("Description") = "RESTfull web-interface for mgstat"
%SYS>set properties("AutheEnabled") = 32 ; 説明を参照してください
%SYS>set properties("Resource") = "PromResource"
%SYS>set properties("DispatchClass") = "my.Mgstat" 
%SYS>write ##class(Security.Applications).Create("/mgstat",.properties)
1

curl を使用してメトリックにアクセスできることを確認する

ファイアウォールで 57772 番ポートを忘れずに開いてください

curl --user PromUser:Secret -XGET http://192.168.42.131:57772/mgstat/5
isc_cache_mgstat_global_refs 347
isc_cache_mgstat_remote_global_refs 0
isc_cache_mgstat_global_remote_ratio 0

curl --user PromUser:Secret -XGET http://192.168.42.132:57772/mgstat/5
isc_cache_mgstat_global_refs 130
isc_cache_mgstat_remote_global_refs 0
isc_cache_mgstat_global_remote_ratio 0
...

Prometheus からメトリックにアクセスできることを確認する

Prometheus は 9090 番ポートをリッスンします。 まずは [Targets] のステータスを確認しましょう。

その後、任意のメトリックを確認してください。

1 つのメトリックを表示する

ここでは 1 つのメトリック(isc_cache_mgstat_global_refs)を例としてグラフに表示します。 まず、ダッシュボードを更新し、そこにグラフを挿入する必要があります。 そのためには Grafana(http://localhost:3000、ログイン/パスワードは admin/TopSecret)に移動し、新しいダッシュボードを追加してください。

グラフを追加します。

[Panel title]、[Edit] の順にクリックし、グラフを編集します。

Prometheus をデータソースとして設定し、isc_cache_mgstat_global_refs メトリックを選択します。 解像度は 1/1 に設定します。

このグラフに名前を付けましょう。

凡例を追加します。

ウィンドウの上部にある [Save] ボタンをクリックし、ダッシュボードの名前を入力します。

最終的には次のように表示されます。

すべてのメトリックを表示する

残りのメトリックも同じように追加しましょう。 2 つのテキストメトリックがあります(Singlestat)。 その結果、次のダッシュボードが表示されます(ここでは上部と下部に分けて掲載しています)。

次の 2 つは明らかに問題があるように思われます。

— 凡例のスクロールバー(サーバーの数が増えるとスクロールバーが長くなります)。

— Singlestat パネルにデータがありません(値が単一であることを意味します)。 私たちには 2 台のサーバーとそれに対応する 2 つの値があります。

テンプレートを使用する

インスタンスにテンプレートを導入し、これらの問題を解決してみましょう。 そのためにはインスタンスの値を格納する変数を作成し、ルールに従って Prometheus へのリクエストを少しだけ編集する必要があります。 つまり、“instance” 変数を作成した後に "isc_cache_mgstat_global_refs" リクエストの代わりに"isc_cache_mgstat_global_refs{instance="[[instance]]"}" を使用する必要があります。

変数を作成します。

Prometheus へのリクエストでは、各メトリックからインスタンスラベルの値を選択しましょう。 画面の下のほうで 2 つのインスタンスの値が識別されていることがわかります。 [Add] ボタンをクリックしてください。

ダッシュボードの上部に、使用可能な値を持つ変数が追加されました。

次に、ダッシュボードの各パネルのリクエストにこの変数を追加しましょう。つまり、"isc_cache_mgstat_global_refs" のようなリクエストを "isc_cache_mgstat_global_refs{instance="[[instance]]"}" に変更します。 最終的なダッシュボードは次のようになります(インスタンス名は意図的に凡例の横に残されています)。

Singlestat パネルが機能するようになりました。

このダッシュボードのテンプレートは GitHub からダウンロードできます。 テンプレートを Grafana にインポートする手順はこの記事のパート 1で説明しています。

最後に、サーバー 192.168.42.132 を 192.168.42.131 の ECP クライアントにして ECP トラフィックを生成するためのグローバルを作成しましょう。 次のように、ECP クライアントの監視が機能していることがわかります。

まとめ

^mgstat の結果を Excel で表示する代わりに、見栄えの良いグラフを使ったダッシュボードをオンラインで使用することができます。 デメリットは、^mgstat の代替バージョンを使用しなければならないことです。 一般的には元になるツールのコードは変更される可能性がありますが、その事は考慮されていません。 ただし、非常に楽な方法で Caché のパフォーマンスを監視することができます。

最後までお読みいただき、ありがとうございました!

つづく...

追伸

デモ(1 つのインスタンス用)はこちらで利用できます。ログイン/パスワードは必要ありません。

0
0 468
記事 Toshihiko Minamoto · 10月 14, 2020 9m read


こんにちは!

この記事では、IRIS から Caché、Ensemble、HealthShare など、InterSystems の製品で使用されるクラスやその構造を理解するのに役立つツールの概要を簡単にまとめています。

つまり、そのツールはクラスやパッケージ全体を視覚化し、クラス間の相対関係を示し、ディベロッパーやチームリーダーに必要な情報をすべて提供してくれるので、わざわざ Studio に移動してコードを調べる必要が省けます。

InterSystems の製品について情報を集めている方からたくさんのプロジェクトをレビューしている方、または単純に InterSystems Technology ソリューションの新機能に興味がある方まで、ObjectScript Class Explorer の概要をぜひお読みください!

InterSystems 製品のご紹介

以前は Caché として知られた IRIS はマルチレベルの DBMS です。 SQL クエリを使ってアクセスしたり、さまざまなプログラミング言語のインターフェースを使い、保管されているオブジェクトやプロシージャを操作したりできます。 ですが、DBMS に組み込まれているネイティブ言語の ObjectScript (COS) を使ってアプリケーションを開発することが、常に最初の選択肢として選ばれます。

Caché は DBMS レベルのクラスに対応しています。 クラスには主に、Persistent(データベースに保管できる) と Registered(データベースに保管されないが、プログラムやハンドラーとしての役割を果たす)の2 種類があります。 また、Serial(アドレスなどの複雑なデータ型を作成するために Persistent クラスに統合して使えるクラス) 、DataType(ユーザー定義のデータ型を作成する際に使用される) 、Index、View、Stream などの特殊なタイプもいくつかあります。

Class Explorer の概要

Caché Class Explorer は、Caché クラスの構造をダイアグラムとして視覚化し、クラスやすべての関連情報 (メソッドのコード、クエリ、xData ブロック、コメント、ドキュメンテーション、さまざまなクラス要素のキーワードなど) の依存関係を示すツールです。

機能

Caché にはクエリ、xData ブロック、メソッドやプロパティの多数のキーワード (System、ZenMethod、Hidden、ProcedureBlock など)、親子関係、一対多関係、クラス型といった、スタンダードな UML ではサポートされていないが、Caché には重要な一連のエンティティが存在するため、Class Explorer は拡張バージョンの UML 表記を使って視覚化を実行します。

Caché Class Explorer (バージョン1.14.3) では、次のことができます。

  • パッケージの階層、クラスのダイアグラム、パッケージ全体を表示する。
  • 表示されたダイアグラムの外観を編集する。
  • クラスのダイアグラムのその時点でのイメージを保存する。
  • ダイアグラムのその時点での外観を保存し、後で復元する。
  • ダイアグラムやクラスツリーに表示されるキーワードを使って検索を行う。
  • ツールヒントを使ってクラス、そのプロパティ、メソッド、パラメーター、クエリ、xData ブロックの完全な情報を確認する。
  • メソッドのコード、クエリ、または xData ブロックを表示する。
  • グラフィックアイコンを含むダイアグラム要素の表示を有効または無効にする。

この記事の残りの内容をよく理解していただけるよう、Class Explorer で視覚化されたクラスを見てみましょう。 それでは、例の 1 つとして、「SAMPLES」ネームスペースの「Cinema」パッケージを表示してみましょう。

詳細および機能の概要 

左側のサイドバーにパッケージツリーがあります。 パッケージ名にカーソルを合わせると、その右側にボタンが表示されるので、それをクリックしてパッケージ全体を表示します。 パッケージツリーの中からクラスを選択し、リンクされているクラスと一緒にレンダリングします。

Class Explorer で表示できるクラス間の依存関係には種類がいくつかあります。

  1. 継承。 継承先クラスを指す白い矢印で示されます。
  2. アソシエーションまたはクラス同士の関係。クラスのフィールドに別クラスのタイプが含まれている場合、ダイアグラムビルダーはこれをアソシエーション関係として示します。
  3. 親子関係と 一対多関係。データの整合性を維持するためのルール。

各関係にカーソルを合わせると、その関係を生み出すプロパティが強調表示されます。

Class Explorer は現在のパッケージの外に存在するクラス間の依存関係までは表示しません。 表示されるのは現在のパッケージ内のクラスのみです。また、Class Explorer によるクラス検索に制限を設定する場合は、「依存関係レベル」設定を使います。

クラスは長方形として表示され、以下の 6 つのセクションに分割されます。

  1. クラス名:クラス名にカーソルを合わせると、作成日、変更日、コメント、クラスに割り当てられたすべてのキーワードが表示されます。 クラスヘッダーをダブルクリックすると、そのドキュメンテーションが表示されます。
  2. クラスパラメーター: すべての割り当てられたパラメーターがタイプ、キーワード、コメントと共に表示されます。 斜体で表記されるパラメーターやすべてのプロパティは、カーソルを合わせるとツールヒントが表示されます。
  3. クラスプロパティ:クラスプロパティはパラメーターに似ています。
  4. メソッド:メソッドをクリックすれば、ソースコードが表示されます。 COS 構文は強調表示されます。
  5. クエリ:メソッドと同様に、クリックすればソースコードが表示されます。
  6. xData ブロック: 主に XML データで構成されるブロック クリックすると、フォーマットされたソースコードがブロックとして表示されます。

デフォルトで、各クラスは多数のグラフィックアイコンと一緒に表示されます。 各アイコンの意味は、画面の右上隅にある「ヘルプ」ボタンをクリックすれば分かります。 多少厳密な UML 表記をデフォルトで表示する必要がある場合や、クラスセクションを表示する場合は、グラフィックアイコンを設定セクションで無効にすることができます。

非常に大きなダイアグラムを使い慣れていない場合は、ダイアグラムのクイック検索機能を使うことができます。 入力したキーワードの一部を含むクラスが強調表示されます。 次のマッチにジャンプするには、Enter キーを押すか、検索ボタンをもう一度クリックします。

最後に、ダイアグラムの編集をすべて完了し、不要な関係をすべて削除し、各要素をそれぞれの適切な位置に配置して、希望通りの外観が出来上がれば、左下隅にある「ダウンロード」ボタンをクリックして保存します。

ピンボタン をアクティブにすると、クラス (またはパッケージ) の現在のセットのダイアグラムに配置されている要素の位置が保存されます。 例えば、クラス A と B を選択し、そのビューをピンボタンで保存すると、ブラウザーやマシンを再起動した後でも、クラス A と B を選択すれば完全に同じビューが表示されます。 しかし、クラス A だけを選択した場合は、デフォルトのレイアウトで表示されます。

インストール

Caché Class Explorer をインストールするには、最新リリース版 の XML パッケージだけをお好きなネームスペースにインポートしてください。 インポートが完了すると、「hostname / ClassExplorer /」という名前の新しい Web アプリ (最後のスラッシュは必須です) が表示されます。

インストールの詳しい手順

  1. Caché Class Explorer の最新リリースを含むアーカイブをダウンロードします。
  2. 「Cache/CacheClassExplorer-vX.X.X.xml」という名前の XML ファイルを抽出します。
  3. 以下のいずれかの方法でパッケージをお好きなネームスペースにインポートします。
    1. XML ファイルを Studio にドラッグする。
    2. System Management Portal を使用する場合: System Explorer -> Classes -> Import と順に移動し、ローカルファイルへのパスを指定する。
    3. ターミナルコマンドを使用する場合: do ##class(%Installer.Installer).InstallFromCommandLine(“Path/Installer.cls.xml”);
  4. インポートログを読み、問題がなければ「http://hostname/ClassExplorer/」から Web アプリケーションを開くことができます。 問題が発生した場合は、以下を確認してください。
    1. このネームスペースにクラスをインポートする権限があるか。
    2. Web アプリケーションのユーザーは異なるネームスペースへのアクセス権を持っているか。
    3. エラー 404 が表示される場合は、URL の最後に「/」を追加しているか。

他のスクリーンショット

[スクリーンショット 1] DSVRDemo パッケージ。クラス名にカーソルを合わせた状態。

[スクリーンショット 2] DataMining パッケージ。ダイアグラムでキーワード「TreeInput」を検索中。

[スクリーンショット 3] JavaDemo.JavaListSample クラスのメソッドコードを表示したビュー。

[スクリーンショット 4] ClassExplorer.Router クラスの XData ブロックのコンテンツを表示中。

Class Explorer の機能を標準の SAMPLES ネームスペース デモでお試しください。 プロジェクトのビデオレビューは こちら からご覧ください。

フィードバックやご提案、コメントはこちら、もしくは GitHub リポジトリからお寄せください。 どうぞお楽しみください!

0
0 395