0 フォロワー · 21 投稿

監視は、ソフトウェアアプリケーションのパフォーマンスおよびハイアベイラビリティについての制御および管理のプロセスです。

記事 Toshihiko Minamoto · 11月 5, 2025 8m read

新しい InterSystems IRIS® Cloud SQL と InterSystems IRIS® Cloud IntegratedML® クラウド製品のユーザーであり、デプロイメントのメトリクスにアクセスして独自の可観測性プラットフォームに送信しようと考えている方のために、メトリクスを Google Cloud Platform Monitoring(旧称 StackDriver)に送信して手っ取り早く行う方法をご紹介します。

クラウドポータルには、概要メトリクス表示用のトップレベルのメトリクスが含まれており、ユーザーに公開されているメトリクスエンドポイントを使用しますが、ある程度探索しなければ、そこにあることには気づきません。

🚩 このアプローチは、「今後名前が付けられる予定の機能」を利用している可能性があるため、それを踏まえると将来性があるものではなく、確実に InterSystemsでサポートされているアプローチではありません。


では、より包括的なセットをエクスポートしたい場合はどうでしょうか?この技術的な記事/例では、メトリクスを取得して可観測性に転送する方法を紹介します。Open Telemetry Collector を使用して、任意のメトリクスターゲットを取得し、任意の可観測性プラットフォーム送信できるように、ニーズに合わせて変更することができます。

上記の結果に導く仕組みは多数の方法で得られますが、ここでは Kubernetes pod を使用して、1 つのコンテナーで Python スクリプトを実行し、もう 1 つのコンテナーで Otel を実行して、メトリクスのプルとプッシュを行います... 自分のやり方を選択することはできますが、この例と記事では、k8s を主人公に Python を使って行います。

手順:

  • 前提条件
  • Python
  • コンテナー
  • Kubernetes
  • Google Cloud Monitoring

前提要件:

  • IRIS®  Cloud SQL の有効なサブスクリプション
  • 実行中の 1 つのデプロイメント(オプションで Integrated ML を使用)
  • 環境に提供するシークレット

環境変数

 
 シークレットの取得
この内容は少し複雑で本題から少し外れているためティーザーに入れましたが、これがシークレットの生成に必要なる値です。
ENV IRIS_CLOUDSQL_USER 'user'
ENV IRIS_CLOUDSQL_PASS 'pass'

☝ これは https://portal.live.isccloud.io の認証情報です。

ENV IRIS_CLOUDSQL_USERPOOLID 'userpoolid'
ENV IRIS_CLOUDSQL_CLIENTID 'clientid'
ENV IRIS_CLOUDSQL_API 'api'

☝ これはブラウザの開発ツールから取得する必要があります。

  • `aud` = clientid
  • `userpoolid`= iss
  • `api` = request utl

ENV IRIS_CLOUDSQL_DEPLOYMENTID 'deploymentid'

☝これはクラウドサービスポータルから取得できます

 

Python:

以下に、クラウドポータルからメトリクスを取得し、それを Otel Collectorが取得するメトリクスとしてローカルにエクスポートする Python ハッキングを示します。

 
iris_cloudsql_exporter.py
import time
import os
import requests
import json

from warrant import Cognito from prometheus_client.core import GaugeMetricFamily, REGISTRY, CounterMetricFamily from prometheus_client import start_http_server from prometheus_client.parser import text_string_to_metric_families

classIRISCloudSQLExporter(object):definit(self): self.access_token = self.get_access_token() self.portal_api = os.environ['IRIS_CLOUDSQL_API'] self.portal_deploymentid = os.environ['IRIS_CLOUDSQL_DEPLOYMENTID']

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">collect</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-comment"># Requests fodder</span>
    url = self.portal_api
    deploymentid = self.portal_deploymentid
    print(url)
    print(deploymentid)

    headers = {
        <span class="hljs-string">'Authorization'</span>: self.access_token, <span class="hljs-comment"># needs to be refresh_token, eventually</span>
        <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
    }

    metrics_response = requests.request(<span class="hljs-string">"GET"</span>, url + <span class="hljs-string">'/metrics/'</span> + deploymentid, headers=headers)
    metrics = metrics_response.content.decode(<span class="hljs-string">"utf-8"</span>)

    <span class="hljs-keyword">for</span> iris_metrics <span class="hljs-keyword">in</span> text_string_to_metric_families(metrics):
        <span class="hljs-keyword">for</span> sample <span class="hljs-keyword">in</span> iris_metrics.samples:

            labels_string = <span class="hljs-string">"{1}"</span>.format(*sample).replace(<span class="hljs-string">'\''</span>,<span class="hljs-string">"\""</span>)
            labels_dict = json.loads(labels_string)
            labels = []

            <span class="hljs-keyword">for</span> d <span class="hljs-keyword">in</span> labels_dict:
                labels.extend(labels_dict)
            <span class="hljs-keyword">if</span> len(labels) &gt; <span class="hljs-number">0</span>:
                g = GaugeMetricFamily(<span class="hljs-string">"{0}"</span>.format(*sample), <span class="hljs-string">'Help text'</span>, labels=labels)
                g.add_metric(list(labels_dict.values()), <span class="hljs-string">"{2}"</span>.format(*sample))
            <span class="hljs-keyword">else</span>:
                g = GaugeMetricFamily(<span class="hljs-string">"{0}"</span>.format(*sample), <span class="hljs-string">'Help text'</span>, labels=labels)
                g.add_metric([<span class="hljs-string">""</span>], <span class="hljs-string">"{2}"</span>.format(*sample))
            <span class="hljs-keyword">yield</span> g

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_access_token</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-keyword">try</span>:
        user_pool_id = os.environ[<span class="hljs-string">'IRIS_CLOUDSQL_USERPOOLID'</span>] <span class="hljs-comment"># isc iss </span>
        username = os.environ[<span class="hljs-string">'IRIS_CLOUDSQL_USER'</span>]
        password = os.environ[<span class="hljs-string">'IRIS_CLOUDSQL_PASS'</span>]
        clientid = os.environ[<span class="hljs-string">'IRIS_CLOUDSQL_CLIENTID'</span>] <span class="hljs-comment"># isc aud </span>
        print(user_pool_id)
        print(username)
        print(password)
        print(clientid)
        
        <span class="hljs-keyword">try</span>:
            u = Cognito(
                user_pool_id=user_pool_id,
                client_id=clientid,
                user_pool_region=<span class="hljs-string">"us-east-2"</span>, <span class="hljs-comment"># needed by warrant, should be derived from poolid doh</span>
                username=username
            )
            u.authenticate(password=password)
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> p:
            print(p)
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(e)

    <span class="hljs-keyword">return</span> u.id_token

ifname == 'main':

start_http_server(<span class="hljs-number">8000</span>)
REGISTRY.register(IRISCloudSQLExporter())
<span class="hljs-keyword">while</span> <span class="hljs-keyword">True</span>:
    REGISTRY.collect()
    print(<span class="hljs-string">"Polling IRIS CloudSQL API for metrics data...."</span>)
    <span class="hljs-comment">#looped e loop</span>
    time.sleep(<span class="hljs-number">120</span>)</code></pre>

 

Docker:

 
Dockerfile
FROM python:3.8ADD src /src
RUN pip install prometheus_client
RUN pip install requests
WORKDIR /src
ENV PYTHONPATH '/src/'ENV PYTHONUNBUFFERED=1ENV IRIS_CLOUDSQL_USERPOOLID 'userpoolid'ENV IRIS_CLOUDSQL_CLIENTID 'clientid'ENV IRIS_CLOUDSQL_USER 'user'ENV IRIS_CLOUDSQL_PASS 'pass'ENV IRIS_CLOUDSQL_API 'api'ENV IRIS_CLOUDSQL_DEPLOYMENTID 'deploymentid'RUN pip install -r requirements.txt
CMD ["python" , "/src/iris_cloudsql_exporter.py"]
docker build -t iris-cloudsql-exporter .
docker image tag iris-cloudsql-exporter sween/iris-cloudsql-exporter:latest
docker push sween/iris-cloudsql-exporter:latest


デプロイメント:

k8s、ネームスペースを作成します:

kubectl create ns iris

k8s、シークレットを追加します:

kubectl create secret generic iris-cloudsql -n iris \
    --from-literal=user=$IRIS_CLOUDSQL_USER \
    --from-literal=pass=$IRIS_CLOUDSQL_PASS \
    --from-literal=clientid=$IRIS_CLOUDSQL_CLIENTID \
    --from-literal=api=$IRIS_CLOUDSQL_API \
    --from-literal=deploymentid=$IRIS_CLOUDSQL_DEPLOYMENTID \
    --from-literal=userpoolid=$IRIS_CLOUDSQL_USERPOOLID

otel、構成を作成します:

apiVersion: v1
data:
  config.yaml: |
    receivers:
      prometheus:
        config:
          scrape_configs:
          - job_name: 'IRIS CloudSQL'
              # Override the global default and scrape targets from this job every 5 seconds.
            scrape_interval: 30s
            scrape_timeout: 30s
            static_configs:
                    - targets: ['192.168.1.96:5000']
            metrics_path: /
exporters:
  googlemanagedprometheus:
    project: "pidtoo-fhir"
service:
  pipelines:
    metrics:
      receivers: [prometheus]
      exporters: [googlemanagedprometheus]

kind: ConfigMap metadata: name: otel-config namespace: iris

k8s、otel 構成を configmap としてロードします:

kubectl -n iris create configmap otel-config --from-file config.yaml

k8s、ロードバランサー(確実にオプション)、MetalLB をデプロイします。これはクラスタ外部からグスクレイピングして検査するために行っています。

cat <<EOF | kubectl apply -f -n iris -
apiVersion: v1
kind: Service
metadata:
  name: iris-cloudsql-exporter-service
spec:
  selector:
    app: iris-cloudsql-exporter
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 5000
    targetPort: 8000
EOF

gcp、Google Cloud へのキーが必要です。サービスアカウントにスコープを設定する必要があります

  • roles/monitoring.metricWriter
kubectl -n iris create secret generic gmp-test-sa --from-file=key.json=key.json

k8s; deployment/pod そのもの。2 つのコンテナー:

 
deployment.yaml
kubectl -n iris apply -f deployment.yaml

実行

特に問題がなければ、ネームスペースを詳しく調べて、状況を確認してみましょう。

✔ GCP と Otel 用の 2 つの configmap

 

✔ 1 つのロードバランサー

 

✔ 1 つの pod、2 つのコンテナーが正しくスクレイピングされます

  

Google Cloud Monitoring

可観測性を調べてメトリクスが正しく受信されていることを確認し、可観測性を高めましょう!

 

0
0 20
記事 Toshihiko Minamoto · 10月 24, 2024 8m read

CI/CD シリーズの新しい章へようこそ。ここでは、InterSystems テクノロジーと GitLab を使用したソフトウェア開発の様々な可能なアプローチを取り上げています。 今回も相互運用性について説明を続けますが、特に相互運用性デプロイの監視に焦点を当てます。 まだアラートをすべての相互運用性プロダクションにセットアップしていない場合は、それをセットアップしてエラーとプロダクションの状態についての一般的なアラートを取得できるようにしてください。

非活動タイムアウトは、すべての相互運用性ビジネスホストに共通する設定です。 ビジネスホストは、「Inactivity Timeout(非活動タイムアウト)」フィールドに指定された秒数以内にメッセージを受信しない場合に非アクティブステータスになります。 プロダクションの監視サービスはプロダクション内のビジネスサービスとビジネスオペレーションのステータスを定期的に確認し、非活動タイムアウト期間内にアクティビティがない場合にその項目を「非アクティブ」にマークします。 デフォルト値は 0(ゼロ)です。 この設定が 0 である場合、ビジネスホストはアイドル状態がどれほど続いても Inactive にマークされることはありません。

これはアラートを生成し、構成されたアラートと合わせてプロダクションの問題に関するリアルタイム通知を可能にするため、非常に便利な設定です。 ビジネスホストがアイドル状態である場合、プロダクション、統合、またはネットワーク接続に調べる価値のある問題がある可能性があります。 ただし、ビジネスホストには一定時間の非活動タイムアウトを 1 つしか設定できないため、夜間、週末、休日などのトラフィックの少ない既知の期間中に不要なアラートを生成する可能性があります。 この記事では、動的な非活動タイムアウトを実装するためのいくつかのアプローチを説明します。 機能する例(現在ある顧客サイトの本番環境で実行しているもの)を紹介していはいますが、この記事は独自の動的な非活動タイムアウトの実装を構築するためのガイドラインを紹介することを目的としているため、ここに提案するソリューションを唯一の代替手法と見なさないようにしてください。

構想

相互運用性エンジンは、各ビジネスホストをサブスクリプトとして、最新のアクティビティのタイムスタンプを値として含む特殊な HostMonitor グローバルを維持しています。 非活動タイムアウトを使用する代わりに、このグローバルを監視し、HostMonitor の状態に基づいてアラートを生成することにします。 HostMonitor は、非活動タイムアウト値が設定されているかに関係なく維持されます。常にオンの状態です。

実装

まず初めに、以下のようにして HostMonitor グローバルを反復処理します。

Set tHost=""
For { 
  Set tHost=$$$OrderHostMonitor(tHost) 
  Quit:""=tHost
  Set lastActivity = $$$GetHostMonitor(tHost,$$$eMonitorLastActivity)
}

監視サービスを作成するには、各ビジネスホストに対して以下のチェックを実行する必要があります。

  1. ビジネスホストが完全に動的非活動タイムアウトのスコープで管理されるかを決定します(たとえば、トラフィックの多い hl7 インターフェースには通常の非活動タイムアウトを適用できます)。
  2. ビジネスホストがスコープ内である場合、最後のアクティビティからの時間を計算する必要があります。
  3. 次に、非活動時間といくつかの条件(日中/夜間、曜日)に基づいて、アラートを送信するかを決定する必要があります。
  4. アラートレコードを送信する場合は、アラートを 2 回送信しないように、最後のアクティビティの時間を記録する必要があります。

すると、コードは以下のようになります。

Set tHost=""
For { 
  Set tHost=$$$OrderHostMonitor(tHost) 
  Quit:""=tHost
  Continue:'..InScope(tHost)
  Set lastActivity = $$$GetHostMonitor(tHost,$$$eMonitorLastActivity)
  Set tDiff = $$$timeDiff($$$timeUTC, lastActivity)
  Set tTimeout = ..GetTimeout(tDayTimeout)
  If (tDiff > tTimeout) && ((lastActivityReported="") || ($system.SQL.DATEDIFF("s",lastActivityReported,lastActivity)>0)) {
    Set tText = $$$FormatText("InactivityTimeoutAlert: Inactivity timeout of '%1' seconds exceeded for host '%2'", +$fn(tDiff,,0), tHost)
    Do ..SendAlert(##class(Ens.AlertRequest).%New($LB(tHost, tText)))
    Set $$$EnsJobLocal("LastActivity", tHost) = lastActivity
  } 
}

カスタムロジックを実際に格納している InScopeGetTimeout メソッドを実装したら、完了です。 この例では、日中のタイムアウト(Day Timeout、これはビジネスホストごとに異なる可能性がありますが、デフォルト値があります)と夜間のタイムアウト(Night Timeout、追跡されているすべてのビジネスホストに共通)があるため、ユーザーは以下の設定を指定する必要があります。

  • スコープ: ビジネスホスト名(または名前の一部)とそれぞれのカスタムの DayTimeout 値を組み合わせた、行あたり 1 つのリスト。 スコープ内のビジネスホストのみが追跡されます(少なくとも 1 つのスコープに対して $find(host, scope) 条件を満たす必要があります)。 すべてのビジネスホストを監視する場合は空白のままにします。 例: OperationA=120
  • DayStart: 00:00:00 からの秒。日中はこの後に開始します。 DayEnd より少ない数値である必要があります。 すなわち、 06:00:00 AM は 6*3600 = 21600 となります。
  • DayEnd: 00:00:00 からの秒。日中はこの後に終了します。 DayStart より大きな数値である必要があります。 すなわち、 08:00:00 PM は (12+8)*3600 = 72000 となります。
  • DayTimeout: 日中にアラートを発生させるための秒単位のデフォルトのタイムアウト値。
  • NightTimeout: 夜間にアラートを発生させるための秒単位のタイムアウト値。
  • WeekendDays: 週末と見なされる曜日。 カンマ区切りです。 週末の場合、NightTimeout が 1 日 24 時間適用されます。 例: 1,7。日付の DayOfWeek 値は、$SYSTEM.SQL.Functions.DAYOFWEEK(date-expression) を実行してチェックします。 デフォルトでは、返される値は以下の曜日を表します: 1 — 日曜日、2 — 月曜日、3 — 火曜日、4 — 水曜日、5 — 木曜日、6 — 金曜日、7 — 土曜日。

完全なコードを確認できますか、特に興味深い点はないと思います。 単に InScope と GetTimeout メソッドを実装しているだけです。 他の基準を使用して、InScope と GetTimeout メソッドを必要に応じて調整できます。

問題

言及する問題は 2 つあります。

  • 非アクティブビジネスホストには黄色いアイコンがない(ホストの非活動タイムアウト設定値がゼロであるため)。
  • Out-of-host 設定 - 開発者は新しいビジネスホストを追加するたびにこのカスタム監視サービスを更新して、動的な非活動タイムアウトを使用することを覚えておく必要があります。

代替手法

上記のソリューションを実装する前に、以下のアプローチを探りました。

  1. 日中/夜間が開始するときに InactivityTimeout 設定を変更するビジネスサービスを作成する。 当初、この方法で進もうと考えましたが、主に、InactivityTimeout 設定を変更するたびに影響のあるすべてのビジネスホストを再起動しなければならないなど、多数の問題に遭遇しました。
  2. カスタムアラートプロセッサーに、夜間の InactivityTimeout である場合にアラートを送信する代わりにアラートを抑止するというツールを追加する。 Ens.MonitorServoce からの非活動アラートが LastActivity を更新するため、カスタムアラートプロセッサーからは「実際の」最終アクティビティタイムスタンプを取得する方法がわかりません(Ens.MessageHeader をクエリする以外で)。 また「夜間」である場合、ホストの状態を OK に戻し、まだ夜間の InactivityTimeout でなければアラートを抑止します。
  3. Ens.MonitorService を拡張する。これは、OnMonitor コールバック以外は可能に思えませんでしたが、他の目的には役立ちます。

まとめ

必ずアラートをすべての相互運用性プロダクションにセットアップして、エラーやプロダクションの状態についての一般的なアラートを取得します。 静的な非活動タイムアウトで十分でなければ、動的な実装を簡単に作成できます。

リンク

0
0 53
記事 Toshihiko Minamoto · 2月 27, 2023 7m read

今回は、「孤立メッセージ」について説明します。

孤立メッセージとは何ですか?

すべてのメッセージボディは、メタデータを保持するメッセージヘッダと関連付けらます。ヘッダーには、ソース構成名称、ターゲット構成名称、作成時刻、処理時刻、関連するメッセージボディ参照、セッション情報、メッセージボディのクラス名称、メッセージステータスなどの情報が格納されます。 メッセージボディに対応するヘッダーレコードが存在しない場合、そのメッセージボディは孤立メッセージボディと呼ばれます。ここでは、孤立メッセージボディの原因となる可能性があるものについて説明します。

ヘッダーのみを削除する場合

削除タスクの設定において、BodiesToo メッセージヘッダとともにメッセージボディも削除するかどうかを指定するものです。この設定をOFFにすると、削除タスクはメッセージヘッダーのみを削除し、メッセージボディは残します。これらのメッセージボディは、参照されたヘッダが削除されることから、孤立したレコードとなります。 メッセージヘッダの削除したが、メッセージボディは残している場合、マネジメントポータルでは孤立メッ セージボディを削除する方法はありません。この場合、プログラムによってメッセージボディを削除する必要があります。

 

削除タスクについては、ドキュメントをご参照ください。

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EGMG_purge#EGMG_purge_basic

複合メッセージボディの クラス (オブジェクトの値によるプロパティ)

Ensemble がメッセージボディを削除する際、メッセージボディのオブジェクトの値によるプロパティが削除されるとは限り ません。具体的には、他のオブジェクトを削除するのは、それらがシリアルオブジェクトであるか、子オブジェクト(関係によって定義される)である場合のみである。その他のオブジェクトについては、削除トリガーを定義するか、メッセージボディのクラスで %OnDelete() 方法を実装して、適切に削除を処理する必要があります。

OnDelete実装のサンプルコード

Class Sample.Address Extends %Persistent{
/// 所番地。
Property Street As %String(MAXLEN = 80);
/// 市名。
Property City As %String(MAXLEN = 80);
/// 2文字の州の略称。
Property State As %String(MAXLEN = 2);
/// 米国の5桁のZIP(Zone Improvement Plan)コード。
Property Zip As %String(MAXLEN = 5);
}
Class Sample.Person Extends %Persistent{
/// 本人の名前。
Property Name As %String [ Required ];
/// 本人の社会保障番号。これはパターンマッチで検証さ れます。
Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ];
/// 本人の生年月日。
Property DOB As %Date;
/// 本人の自宅住所。
Property Home As Address;
/// 本人の勤務先住所。
Property Office As Address;
///オブジェクトを削除するためのコールバック。
ClassMethod %OnDelete(oid As %ObjectIdentity) As %Status [ Private ]{
      // プロパティオブジェクトの参照を削除することです。
      Set tSC = $$$OK, tThis = ##class(Sample.Person).%Open(oid)
      If $ISOBJECT(tThis.Home) Set tSC = ##class(Sample.Address).%DeleteId(tThis.Home.%Id())
      If $ISOBJECT(tThis.Office) Set tSC = ##class(Sample.Address).%DeleteId(tThis.Office.%Id())
      Quit tSC
}
///SQL削除のコールバック/トリガー
Trigger OnDelete [ Event = DELETE ]{
      // プロパティ・オブジェクトの参照を削除します。{%%ID} は削除されるレコードの ID を保持します。
      Set tID={%%ID}
      Set tThis = ##class(Sample.Person).%OpenId(tID)
      If $ISOBJECT(tThis.Home) Do ##class(Sample.Address).%DeleteId(tThis.Home.%Id())
      If $ISOBJECT(tThis.Office) Do ##class(Sample.Address).%DeleteId(tThis.Office.%Id())
      Quit
}
}

メッセージオブジェクトが作成さられましたが、他のホストに送信されませんでした。

メッセージが別のホストに送信/転送さ れようとしたら、Ensemble は新しいメッセージヘッダを作成し、対応するメッセージボディを関連付けます。 ビジネスサービス/プロセスで作成されたメッセージボディ/オブジェクトのインスタンスがディスク/データベースに保存され、かつプロダクション内の別のホストに送信されなかった場合、関連するヘッダーがなく、孤立メッセージボディとして残ります。ベストプラクティスは、転送されない限りメッセージボディを作成しないこと、またはオブジェクトのインスタンスの %Save() をコールしないことです (SendRequestSync/SendRequestAsync API は、メッセージをターゲット設定キューに入れる前にそれを保存します)。この方法では、メッセージボディのオブジェクトは、他のホストに送信されない限り、永続化されることはありません。

この問題の最も一般的な原因は、開発者が:

  1. メッセージボディを複製し、複製されたメッセージボディを転送しない場合です。
  2. コンテキスト変数(BPL)で作成されたメッセージボディオブジェクトが転送されない場合です。

孤立メッセージの効果

孤立メッセージは、削除タスクによって削除されません。これらのメッセージはディスクスペースを消費し、その数が増えるにつれて、ディスク使用量も増加します。ディスク使用量は、メッセージボディのデータだけでなく、これらの孤立メッセージボディレコードのインデックス/サーチテーブルのエントリも含めて使用されることになります。

孤立したメッセージの特定

孤立したメッセージの存在は、メッセージヘッダとボディを照会することで 特定することができます。各メッセージヘッダは、対応するメッセージボディを参照します。メッセージボディのオブジェクトIdの参照はヘッダのMessageBodyIdプロパティに、メッセージボディのクラス名はヘッダのMessageBodyClassNameプロパティに格納されます。

HL7 メッセージ・テーブルで孤立メッセージを見つけるためのクエリの例:

SELECT HL7.Id  FROM EnsLib_HL7.Message HL7

   LEFT JOIN Ens.MessageHeader hdr

   ON HL7.Id=hdr.MessageBodyId

   WHERE hdr.MessageBodyId IS NULL

上記のクエリは、対応するヘッダを持たないすべての HL7 メッセージを返します。このクエリは、メッセージボディのテーブル名称を置き換えるだけで、他のメッセージタイプに対するクエリに変更することができます。

孤立メッセージの削除

マネジメントポータルでは孤立メッ セージボディを削除する方法はありません。この場合、プログラムによってメッセージボディを削除する必要があります。ENSDEMO データベースでは、Demo.Util.CleanupSet というクラスが、これを実行する方法の例を示してくれます。このルーチンは、オブジェクトのプロパティ参照も含めて深く削除します。

孤立メッセージを削除するためのルーチンがもう一つありますが、このルーチンは深い削除はしませんし、メッセージボディの削除だけを助けます。ソースをダウンロードするために、以下にgithubへのリンクを載せておきます。

https://gist.github.com/suriyasv/2ed7f2dbcfd8c79f3b9938762c17c0b5

ベストプラクティスは、

  1. 孤立メッセージを防ぐために、常にプログラミングエラーを避けることです(前述)。
  2. BodiesToo設定をOFFにした削除タスクを、ボディが必要な場合のみ設定し、これらのメッセージボディはプログラムによってのみ削除できることを認識することです。
  3. 永続オブジェクトプロパティに対するRelationshipsまたはOnDelete実装することです。

この記事が、プロダクションを構築する際にお役に立てば幸いです。また、ご不明な点などございましたら、お知らせください。ありがとうございました。

0
0 169
記事 Mihoko Iijima · 1月 5, 2023 14m read

これは InterSystems FAQ サイトの記事です。

システムモニタの中の「アプリケーションモニタ」を利用することで、ユーザが定義した特定の監視対象に対してチェックを行い特定の条件に合致した場合に通知を行ったり、メッセージログ(コンソールログ)に情報を出力したり、ユーザが定義するアクションを指定できます。

<メモ>
アプリケーションモニタはインストールにより準備されますが、ユーザが付属のアプリケーション・モニタ・クラスを有効化するまで動作しないモニタです。
付属のアプリケーションモニタには、監査のカウントやイベント詳細を収集するもの、ディスクの容量を監視するものなどが含まれます。

詳細は、以下ドキュメントをご参照ください。
【IRIS】アプリケーション・モニタのメトリック
アプリケーション・モニタのメトリック

作成手順は以下の通りです。

  1. %SYSネームスペースにアプリケーションモニタ用クラスを作成する
  2. 作成した1のクラスを、システムモニタのアプリケーションモニタ有効化メニューで有効化する
  3. 収集のインターバルを設定する(秒単位)
  4. システムモニタを再起動する
0
0 209
記事 Toshihiko Minamoto · 8月 2, 2022 5m read

サポートではこのような質問をたまに受けることがあります。何かが、または誰かが、想定以上のライセンスを使用しており、それを調べなければなりません。 

調べるタイミングは2回あります。 1 つは、アプリケーションが動作しないか、ターミナル経由で接続しようとすると次のような「愛くるしい」メッセージが表示され、ライセンスが使い果たされていることに気づいたときです。

<LICENSE LIMIT EXCEEDED> メッセージ: 


2 つ目のタイミングは、アプリケーションを使用できなかったことがあったという苦情をエンドユーザーから受けたときですが、問題が発生しているのを確認するには遅すぎます。 こういった場合には通例、messages.log に「License Limit exceeded xxxx times」というメッセージが確認されます。

適時に問題をキャッチする

最初のタイミングの場合は、問題が発生している状態を確認できるため、それをキャッチする方法がいくつか考えられます。 

  • システム管理ポータルにログインできるのであれば、[ライセンス]セクションに移動して、何が何を使用しているのかを調べることができます。 
  • -B パラメーターを使用してターミナル経由で接続します(ライセンスがすべて使用中であるため、通常の方法では接続できません)。 この -B パラメーターは、ログインが無効化されたり、ライセンスが使い果たされている場合に、管理者の緊急ログインとして使用されるパラメーターです。
iris session <instance> -B
  • 接続したら、License DumpLocalAll メソッドを実行してすべてのライセンスをダンプ出力し、スロットごとに何が消費しているのかを調べることができます。 このファイルには以下のような内容が含まれます。

ライセンスをダンプ出力して調べれば、ほとんどのお客様にはサポート経由で何が起きているのかを調べる必要はありません。 お客様自身で、想定以上のライセンスを消費しているマシン、ユーザー、またはアプリケーションを特定することができます。

iris への接続方法と License クラスについての詳細は、ドキュメントをご覧ください。 

問題発生後の対処

問題が発生した後に確認されたために問題そのものをキャッチできない 2 つ目のタイミングにおいては、いくつかの方法があります。 

1)messages.log を監視し、ライセンス警告メッセージ(およびその他のメッセージ)が出力された時点で問題をキャッチする

問題が発生したときに警告が通知されれば、1 つ目のタイミングとして対処し、接続して上記のヒントを実行できます。 

レベル 2 メッセージの監視には、非常に便利な ^MONMGRシステムモニター)を使用するのが簡単です。 システムがレベル 2 の警告(ライセンス関連など)を受信すると、メールが送信されます。 警告はすぐに通知されるため、システムに接続し、システム管理ポータル([ライセンス]セクション)やターミナル経由でライセンスの使用状況を確認することができます。 

2)ライセンスの使用状況を messages.log に出力する

ライセンスの使用状況を messages.log で出力・追跡するようにすることができます。 トレースがオンになっている場合、ライセンスがログインまたはログアウトされるたびにそれが messages.log にトレースされるため、プロセス、アクション(ログインまたはログアウト)、ロール(User、CSP、Diagnostic)、および成功または失敗(成功: 0 以外の接続カウント、失敗: 0)を確認することができます。  
ログインの結果フィールドには、新しい接続カウントが含まれます。 ログアウトの結果フィールドは必ず 1 で、成功を示します。
 
以下は、トレースを有効化または無効化にするためのコマンドです。
Do traceon^%SYS.LICENSE   // ライセンスのトレースをオンにします。 

Do traceoff^%SYS.LICENSE // ライセンスのトレースをオフにします。

このようにすると、稼働率の高いシステムでは、messages.log に多くの「ノイズ」が出力されてしまうことに注意してください。 したがって、一部のケースでは、最初のアプローチが推奨されます。 問題の箇所が見つかったら、忘れずにトレースを無効にしてください!

まとめ

先に述べたように、問題が発生した時点でそれをキャッチし、ライセンスのダンプ出力を確認できるようになれば、何がライセンスを消費しているのかを特定するのが非常に簡単になります。 何か変わったことがあればそれに対処する必要がありますが、そうでなければライセンスをさらに購入する必要があるでしょう。これについては別件ですので、WRC や営業部にご連絡ください。

0
0 498
記事 Toshihiko Minamoto · 7月 8, 2022 8m read

皆さん、こんにちは。

IRIS 履歴モニタープロジェクトが更新されました。ZPM とビルトインの REST API /api/monitor/metrics を使用します。

これに伴い、IRIS 2019.4 に含まれた API の機能と可能性を示す CSP ページを新たに作成しました。 そのため、Caché インストールまたは 2019.4 より前の IRISでプロジェクトを使用しているユーザーは、何も気にせずにそのままプロジェクトを使用続けることができます。 

ビルトイン API とその使用方法については、@Murray.Oldfieldが投稿した以下の 2 本の素晴らしい記事をお読みください。 

https://community.intersystems.com/post/monitoring-intersystems-iris-using-built-rest-api

https://community.intersystems.com/post/example-review-monitor-metrics-intersystems-iris-using-default-rest-api

/metrics エンドポイントには多数の使用方法がリストされてはいますが、既存のすべてのグラフにデータソースを指定するには、さらに多くの情報が必要でした。 

その助けになったのがドキュメントです。ドキュメントには、私が求めていたアプリケーション指標の作成方法が説明されていました! 

https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_rest#GCM_rest_metrics_application

カスタムアプリケーション指標を /metrics エンドポイントが返す指標に追加するには、以下を実行します。

  1. %SYS.Monitor.SAM.Abstract を継承する新しいクラスを作成します。
  2. アプリケーションの名前として、PRODUCT パラメーターを定義します。
  3. 目的のカスタム指標を定義する GetSensors() メソッドを実装します。 このメソッドは、成功した場合に $$$OK を返し、SetSensor() メソッドへの呼び出しが 1 つ以上含まれている必要があります。各指標の名前、値、およびオプションのラベルは、このメソッドによって設定されます。

ドキュメントの指示に従って、%SYS.Monitor.SAM.Abstract を継承する diashenrique.historymonitor.util.customSensors クラスを作成します。

/// /metric API のカスタムクラスの例
Class diashenrique.historymonitor.util.customSensors Extends %SYS.Monitor.SAM.Abstract
{

Parameter PRODUCT = "irismonitor";

/// 指標を収集 
Method GetSensors() As %Status
{
    Do ..getDashboardWS(.dashboard)

    Do ..SetSensor("systemuptime",dashboard.SystemUpTime)
    Do ..SetSensor("lastbackup",dashboard.LastBackup)
    Do ..SetSensor("locktable",dashboard.LockTable)
    Do ..SetSensor("journalspace",dashboard.JournalSpace)
    Do ..SetSensor("journalstatus",dashboard.JournalStatus)
    Do ..SetSensor("ecpappserver",dashboard.ECPAppServer)
    Do ..SetSensor("ecpdataserver",dashboard.ECPDataServer)
    Do ..SetSensor("writedaemon",dashboard.WriteDaemon)
    Do ..SetSensor("licensecurrent",dashboard.LicenseCurrent)
    Do ..SetSensor("licensecurrentpct",dashboard.LicenseCurrentPct)
    Do ..SetSensor("licensehigh",dashboard.LicenseHigh)
    Do ..SetSensor("licensehighpct",dashboard.LicenseHighPct)
    Do ..SetSensor("licenselimit",dashboard.LicenseLimit)
    Do ..SetSensor("applicationerrors",dashboard.ApplicationErrors)

    Return $$$OK
}

ClassMethod getDashboardWS(Output dashboard)
{
    New $Namespace
    Set $Namespace = "%SYS"

    Do ##class(Config.Startup).Get(.Prop)
    Set webPort = Prop("WebServerPort")

    Set client = ##class(SYS.WSMon.Client).%New()
    Set client.Location = "http://localhost:"_webPort_"/csp/sys/SYS.WSMon.Service.cls"
    Set dashboard = client.GetDashboard()

    Quit $$$OK
}
}

追加情報のソースについては、SYS.WSMon.Service という Web サービスを選択しました。

この Web サービスには、多数の Web メソッドが用意されており、内容は以下に説明されています。

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_wsmon

すべての準備が整うと、インストーラーマニフェストの最後で、作成したカスタムアプリケーション指標が呼び出されます。

ClassMethod CustomApplicationMetrics() As %Status
{
  New $Namespace
  Set $Namespace = "%SYS"   
  Set status = ##class(SYS.Monitor.SAM.Config).AddApplicationClass("diashenrique.historymonitor.util.customSensors","IRISMONITOR")
  
  Quit status
}

その後、REST API http://localhost:52773/api/monitor/metrics は最終的に以下のようになります。

iris_cpu_pct{id="CSPDMN"} 0
iris_cpu_pct{id="CSPSRV"} 0
iris_cpu_pct{id="ECPWorker"} 0
iris_cpu_pct{id="GARCOL"} 0
iris_cpu_pct{id="JRNDMN"} 0
iris_cpu_pct{id="LICENSESRV"} 0
iris_cpu_pct{id="WDSLAVE"} 0
iris_cpu_pct{id="WRTDMN"} 0
iris_cpu_usage 7
iris_csp_activity{id="127.0.0.1:52773"} 746
iris_csp_actual_connections{id="127.0.0.1:52773"} 10
iris_csp_gateway_latency{id="127.0.0.1:52773"} .585
iris_csp_in_use_connections{id="127.0.0.1:52773"} 1
iris_csp_private_connections{id="127.0.0.1:52773"} 0
iris_csp_sessions 3
iris_cache_efficiency 177.284
iris_db_expansion_size_mb{id="IRISAUDIT"} 0
iris_db_expansion_size_mb{id="IRISLOCALDATA"} 0
iris_db_expansion_size_mb{id="IRISMONITOR"} 0
iris_db_expansion_size_mb{id="IRISSYS"} 0
iris_db_expansion_size_mb{id="IRISTEMP"} 0
iris_db_expansion_size_mb{id="USER"} 0
iris_db_free_space{id="IRISAUDIT"} .27
iris_db_free_space{id="IRISLOCALDATA"} .2
iris_db_free_space{id="IRISMONITOR"} .16
iris_db_free_space{id="IRISSYS"} 9.4
iris_db_free_space{id="IRISTEMP"} 9.6
iris_db_free_space{id="USER"} .38
iris_db_latency{id="IRISAUDIT"} 0.003
iris_db_latency{id="IRISMONITOR"} 0.003
iris_db_latency{id="IRISSYS"} 0.017
iris_db_latency{id="IRISTEMP"} 0.003
iris_db_latency{id="USER"} 0.002
iris_db_max_size_mb{id="IRISAUDIT"} 0
iris_db_max_size_mb{id="IRISLOCALDATA"} 0
iris_db_max_size_mb{id="IRISMONITOR"} 0
iris_db_max_size_mb{id="IRISSYS"} 0
iris_db_max_size_mb{id="IRISTEMP"} 0
iris_db_max_size_mb{id="USER"} 0
iris_db_size_mb{id="USER",dir="/durable/irissys/mgr/user/"} 1
iris_db_size_mb{id="IRISSYS",dir="/durable/irissys/mgr/"} 90
iris_db_size_mb{id="IRISTEMP",dir="/durable/irissys/mgr/iristemp/"} 11
iris_db_size_mb{id="IRISAUDIT",dir="/durable/irissys/mgr/irisaudit/"} 1
iris_db_size_mb{id="IRISMONITOR",dir="/opt/irisapp/IRISMONITOR/"} 11
iris_db_size_mb{id="IRISLOCALDATA",dir="/durable/irissys/mgr/irislocaldata/"} 1
iris_directory_space{id="USER",dir="/durable/irissys/mgr/user/"} 39209
iris_directory_space{id="IRISSYS",dir="/durable/irissys/mgr/"} 39209
iris_directory_space{id="IRISTEMP",dir="/durable/irissys/mgr/iristemp/"} 39209
iris_directory_space{id="IRISAUDIT",dir="/durable/irissys/mgr/irisaudit/"} 39209
iris_directory_space{id="IRISMONITOR",dir="/opt/irisapp/IRISMONITOR/"} 39209
iris_disk_percent_full{id="USER",dir="/durable/irissys/mgr/user/"} 34.45
iris_disk_percent_full{id="IRISSYS",dir="/durable/irissys/mgr/"} 34.45
iris_disk_percent_full{id="IRISTEMP",dir="/durable/irissys/mgr/iristemp/"} 34.45
iris_disk_percent_full{id="IRISAUDIT",dir="/durable/irissys/mgr/irisaudit/"} 34.45
iris_disk_percent_full{id="IRISMONITOR",dir="/opt/irisapp/IRISMONITOR/"} 34.45
iris_ecp_conn 0
iris_ecp_conn_max 2
iris_ecp_connections 0
iris_ecp_latency 0
iris_ecps_conn 0
iris_ecps_conn_max 1
iris_glo_a_seize_per_sec 0
iris_glo_n_seize_per_sec 0
iris_glo_ref_per_sec 32
iris_glo_ref_rem_per_sec 0
iris_glo_seize_per_sec 0
iris_glo_update_per_sec 4
iris_glo_update_rem_per_sec 0
iris_journal_size 1472
iris_journal_space 36141.84
iris_jrn_block_per_sec 0
iris_jrn_entry_per_sec 0
iris_jrn_free_space{id="WIJ",dir="default"} 36141.84
iris_jrn_free_space{id="primary",dir="/durable/irissys/mgr/journal/"} 36141.84
iris_jrn_free_space{id="secondary",dir="/durable/irissys/mgr/journal/"} 36141.84
iris_jrn_size{id="WIJ"} 100
iris_jrn_size{id="primary"} 1
iris_jrn_size{id="secondary"} 0
iris_license_available 3
iris_license_consumed 2
iris_license_percent_used 40
iris_log_reads_per_sec 25
iris_obj_a_seize_per_sec 0
iris_obj_del_per_sec 2
iris_obj_hit_per_sec 5
iris_obj_load_per_sec 0
iris_obj_miss_per_sec 1
iris_obj_new_per_sec 2
iris_obj_seize_per_sec 0
iris_page_space_per_cent_used 0
iris_phys_mem_per_cent_used 96
iris_phys_reads_per_sec 0
iris_phys_writes_per_sec 0
iris_process_count 32
iris_rtn_a_seize_per_sec 0
iris_rtn_call_local_per_sec 37
iris_rtn_call_miss_per_sec 0
iris_rtn_call_remote_per_sec 0
iris_rtn_load_per_sec 0
iris_rtn_load_rem_per_sec 0
iris_rtn_seize_per_sec 3
iris_sam_get_db_sensors_seconds .000745
iris_sam_get_jrn_sensors_seconds .000811
iris_system_alerts 2
iris_system_alerts_new 1
iris_system_state 0
iris_trans_open_count 0
iris_trans_open_secs 0
iris_trans_open_secs_max 0
iris_wd_buffer_redirty 0
iris_wd_buffer_write 0
iris_wd_cycle_time 706
iris_wd_proc_in_global 0
iris_wd_size_write 0
iris_wd_sleep 9006
iris_wd_temp_queue 81
iris_wd_temp_write 0
iris_wdwij_time 496
iris_wd_write_time 209
iris_wij_writes_per_sec 0
irismonitor_applicationerrors 1
irismonitor_ecpappserver OK
irismonitor_ecpdataserver OK
irismonitor_journalspace Normal
irismonitor_journalstatus Normal
irismonitor_lastbackup 
irismonitor_licensecurrent 2
irismonitor_licensecurrentpct 40
irismonitor_licensehigh 2
irismonitor_licensehighpct 40
irismonitor_licenselimit 5
irismonitor_locktable Normal
irismonitor_systemuptime 0d  1h 18m
irismonitor_writedaemon Normal

クラスの PRODUCT パラメーターを覚えていますか?

Parameter PRODUCT = "irismonitor";

このパラメーターが顧客情報の接頭辞になっています。

この例をお楽しみいただけたでしょうか。コードの参考としてご利用ください。

0
0 178
記事 Toshihiko Minamoto · 6月 28, 2022 5m read

皆さん、こんにちは!

職場で持ち上がった単純なリクエストで始めた個人プロジェクトを紹介したいと思います。 

使用している Caché ライセンス数を調べることはできますか? 

コミュニティに掲載されている他の記事を読んでみたところ、David Loveluck が投稿したぴったりの記事が見つかりました。 

APM - Using the Caché History Monitor(APM - Caché 履歴モニターを使用する)
https://community.intersystems.com/post/apm-using-cach%C3%A9-history-monitor

そこで、David の記事を参考に、Caché 履歴モニターを使って、リクエストされた情報を表示して見ました。 

「どのテクノロジーを使用するのか」という疑問に対し 

私は CSP に決定しました。単純で強力なテクノロジーであるため、私が担当するお客様は Caché が単なる MUMPS/ターミナルではないことに気づくでしょう。

ライセンス、データベース増加状況、CSP セッションの履歴を表示するページを作成した後、「システムダッシュボードとプロセス」ページのデザインを新装することにしました。

私の Caché インスタンスではすべてうまく機能します。

でも、IRIS はどうでしょうか? 

Evgeny Shvarov が投稿した以下の記事に従って、

Using Docker with your InterSystems IRIS development repository(InterSystems IRIS 開発リポジトリで Docker を使用する)
https://community.intersystems.com/post/using-docker-your-intersystems-iris-development-repository

コードを Docker 化して GitHub に配置しました。いくつかの手順を踏めば、どなたでも利用できます。


実行方法

このリポジトリでコーディングを始めるには、以下を実行します。

  1. 任意のローカルディレクトリにリポジトリを Clone/git pull します。
    $ git clone https://github.com/diashenrique/iris-history-monitor.git

  2. このディレクトリでターミナルを開き、以下を実行します。
    $ docker-compose build

  3. プロジェクトで IRIS コンテナを実行します。
    $ docker-compose up -d

テスト方法

ブラウザを開いて、以下のアドレスに移動します。

例: http://localhost:52773/csp/irismonitor/dashboard.csp

ユーザー名 _SYSTEM を使用して、ダッシュボードとその他の機能を実行できます。

システムダッシュボード

システムダッシュボードには、以下の項目が表示されます。

  • ライセンス
  • システム時間
  • アプリケーションエラー
  • Cache プロセス
  • CSP セッション
  • ロックテーブル
  • ジャーナル空間
  • ジャーナルのステータス
  • ECP AppServer
  • ECP DataServer
  • Write デーモン
  • Cache の効率
  • 重大なアラート

線グラフウィジェットには、5 秒ごとにポイントがプロットされます。

 システムメニュー

システムプロセス

プロセスフィルター

さまざまなフィルターを使用して、必要な結果を得ることができます。 また、列のヘッダーを Shift + クリックすると、複数の項目で並べ替えることもできます。データグリッドを Excel にエクスポートすることも可能です!

履歴モニター

CSP セッションとライセンスの履歴モニターでは、情報が以下の 3 つのセクションに分かれて表示されます。

  • 5 分毎
  • 毎日
  • 毎時間

データベースの増加が表示できるのは、毎日の情報のみです。

履歴ページでは以下の機能を共通して使用できます。

日付範囲の選択ツール

_デフォルト_値は、「過去 7 日間」です。

グラフ/データテーブル

各セクションの右上に、2 つのボタン(チャート/データテーブル)があります。

データテーブルには、グラフを作成する情報が表示されます。Excel 形式でダウンロード可能です。

Excel には、CSP で定義されたのと同じフォーマット、コンテンツ、およびグループが表示浚えます。

ズーム機能

すべてのグラフにはズームオプションがあるため、情報をより詳細に可視化することができます。

平均と最大

毎時間と毎日のセクションのグラフには、平均値と最大値が表示されます。

平均

最大

どうぞお楽しみください!

0
0 225
InterSystems公式 Toshihiko Minamoto · 5月 1, 2022

この度、インターシステムズはSystem Alerting & Monitoring (SAM) バージョン 1.1 をリリースしました。

SAMとは?

SAMはIRIS標準の 監視 API や ログ・モニタ をGrafana や Prometheus といった使い慣れた業界標準のツールと融合し、IRISの基本的な監視とアラートのソリューションです。

SAMの詳細については System Alerting and Monitoring ガイド をご参照ください。

SAM 1.1 の新機能は?

大きなデータセットを扱う際、Grafana ダッシュボードのグラフのパフォーマンスが向上しています。  SAM 1.0 からアップグレードする場合、データにインデックスが追加されるため、十分なディスクスペースを確保することをお勧めします。

アップグレード時の詳細については、 リリースノート を参照してください。
 

SAM トップページ

SAM インスタンスの詳細ページ

0
0 145
記事 Toshihiko Minamoto · 12月 24, 2021 3m read

皆さん、こんにちは。私の最新のプロジェクトの1つをご紹介します。 Grafana用データソースプラグインです。これは、InterSystems IRISに直接接続して(将来的に)あらゆるデータを収集できるプラグインです。 

機能

  • 定期的に更新される履歴付きのSAM メトリクスで、Grafanaが直接収集したメトリクスを、表示中にリクエストされた場合にのみ表示できます。
  • messages.logとalerts.logを表示します。 
  • ^ERRORSグローバルのアプリケーションエラー

後日追加予定の機能

  • DateTimeフィールドの有無に関わらずテーブルをクエリするすべてのSQL SELECT
  • グローバルからデータを直接表示
  • IRIS側でカスタムSQLクエリを呼び出す
  • MDXクエリ

つまり、アプリケーション内でログを記録するためのカスタムロジックが存在する場合、Grafanaをそれらのログに接続して、そこで表示することが可能ということです。

テスト

自分でテストしてみるには、リポジトリをクローンして、docker-composeで環境を開始します。 docker-compose環境はポート3000、3081、3082を使用して構成されるようになっているため、システム上でこれらのポートがすでに使用されている場合は、docker-compose.ymlファイルでポートを変更してください。

git clone https://github.com/caretdev/grafana-intersystems-datasource.git
cd grafana-intersystems-datasource
docker-compose up -d

イメージをプルしたら、2つのコンテナでGrafanaとIRISが開始します。

Grafanaはhttp://localhost:3000/で開きます。

[DataSources]に移動すると、InterSystems IRIS接続が自動プロビジョニングによって追加されています。

中を覗くと、基本設定付きの単純なフォームと接続を確認するための[Test]ボタンがあります。 IRISが起動すると、緑色の「OK」が表示されます。

ダッシュボードとパネルを作成しましょう

[Query Type]で「Metrics」を選択します。 

例としてiris_db_latencyを選択しましょう

デフォルトの更新間隔は選択した時間間隔に応じますが、クエリオプションの[Min Interval]フィールドで変更することができます。

ログファイルとアプリケーションエラーは、ログの視覚化とともにテーブルとして表示することができます。

プロジェクトに投票してください

プラグインにほかの機能の追加を希望する場合は、私にご連絡ください。

0
0 131
記事 Toshihiko Minamoto · 6月 21, 2021 9m read

この記事では、syslogテーブルについて説明したいと思います。  syslogとは何か、どのように確認するのか、実際のエントリはどのようなものか、そしてなぜそれが重要であるのかについて説明します。  syslogテーブルには、重要な診断情報が含まれることがあります。  システムに何らかの問題が生じている場合に、このテーブルの確認方法とどのような情報が含まれているのかを理解しておくことが重要です。

syslogテーブルとは?

Cachéは、共有メモリのごく一部を使って、関心のある項目をログに記録しています。  このテーブルは、次のようなさまざまな名前で呼ばれています。

  • Cachéシステムエラーログ
  • errlog
  • SYSLOG
  • syslogテーブル

この記事では、単に「syslogテーブル」と呼ぶことにします。

syslogテーブルのサイズは構成可能です。  デフォルトは500エントリで、  10~10,000エントリの範囲で構成できます。  syslogテーブルのサイズを変更するには、システム管理ポータル -> システム管理 -> 構成 -> 追加設定 -> 詳細メモリに移動し、「errlog」行の「編集」を選択します。  そこにsyslogテーブルに必要なエントリの数を入力してください。

syslogテーブルのサイズを変更する理由は?

syslogテーブルが500エントリに構成されていると、501番目のエントリによって最初のエントリが上書きされ、そのエントリの情報は失われてしまいます。  これはメモリ内にあるテーブルであるため、出力を明示的に保存しない限りどこにも永続されません。  また、Cachéを停止した場合には、以下に説明する方法でエントリをconsole.logファイルに保存するように構成していない限り、すべてのエントリは失われてしまいます。

Cachéが多くのエントリをsyslogテーブルに書き込んでおり、問題の診断目的でそのエントリを確認する場合、テーブルのサイズが十分に大きくなければ、エントリは失われてしまいます。  syslogテーブルのDate/Time列を見ると、テーブルが書き込まれた期間を判別できます。  その上で、どれくらいのエントリ数を設定するのかを決めることができます。  個人的には、エントリを失わない数で判断を誤る方が良いと思います。  このことについては、以下でより詳しく説明します。

syslogテーブルの確認方法

syslogテーブルの確認方法にはいくつかあります。

  1. Cachéターミナルプロンプトから、%SYSネームスペースで「Do ^SYSLOG」を実行する。
  2. Cachéターミナルプロンプトから、%SYSネームスペースで「do ^Buttons」を実行する。
  3. 管理ポータル -> システム操作 -> 診断レポートに移動する。
  4. -e1オプションでcstatを実行する。
  5. Cachehungを実行する。
  6. シャットダウン中にsyslogテーブルをconsole.logファイルに書き込むようにCaché を設定し、console.logファイルを確認する。  設定するには、システム管理ポータル -> 構成 -> 追加設定 -> 互換性に移動し、「ShutDownLogErros」行で「編集」を選択します。  Cachéのシャッドダウン中にsyslogのコンテンツをconsole.logに保存する場合は「true」、保存しない場合は「false」を選択します。

syslogのエントリとは?

syslogテーブルの例を以下に示します。  この例は、Cachéターミナルプロンプトで「^SYSLOG」を実行して得られたものです。

%SYS>d ^SYSLOG

Device:
Right margin: 80 =>
Show detail? No => No
Cache System Error Log printed on Nov 02 2016 at  4:29 PM
--------------------------------------------------------
Printing the last 8 entries out of 8 total occurrences.
Err  Process         Date/Time                             Mod     Line      Routine                             Namespace
9     41681038     11/02/2016 04:44:51PM     93        5690     systest+3^systest         %SYS
9     41681038     11/02/2016 04:43:34PM     93        5690     systest+3^systest         %SYS
9     41681038     11/02/2016 04:42:06PM     93        5690     systest+3^systest         %SYS
9     41681038     11/02/2016 04:41:21PM     93        5690     systest+3^systest         %SYS
9     41681038     11/02/2016 04:39:29PM     93        5690     systest+3^systest         %SYS
9     41681036     11/02/2016 04:38:26PM     93        5690     systest+3^systest         %SYS
9     41681036     11/02/2016 04:36:57PM     93        5690     systest+3^systest         %SYS
9     41681036     11/02/2016 04:29:45PM     93        5690     systest+3^systest         %SYS

列の見出しを見ればエントリの各項目が何であるかは明確のようですが、それらについて説明します。

Printing the last 8 entries out of 8 total occurrences

これは、syslogテーブルエントリの一部ではありませんが、確認することは重要なので、ここで説明します。  この行から、いくつのエントリがsyslogテーブルに書き込まれたかがわかります。  この例では、Cachéが起動してから8つのエントリしか書き込まれていません。  エントリ数が少ないのは、これが私のテストシステムであるためです。  「Printing the last 500 entries out of 11,531 total occurrences(発生総数11,531件中最新の500件のエントリを出力)」と書かれていれば、多数のエントリが見逃されていることがわかります。  見逃されたエントリを確認する場合は、テーブルサイズは最大10,000に増やすか、より頻繁にSYSLOGを実行してください。 

Err

これは、関心のあるイベントについてログに記録される情報です。  エラーは必ずOSレベルで/usr/include/errno.h(Unix)から発生しているものと思われがちですが、実際には「必ず」ではなく、ほとんどの場合です。  ログに記録できるものであれば、何でも記録されます。  たとえば、診断アドホックのデバッグ情報、C変数の値、エラーコードの定義(10000を超えるもの)などを記録することができます。  どのように区別すればよいのでしょうか。  実際に、エントリのModLineに示される、Cコードの行を確認する必要があります。  つまり、InterSystemsに連絡を取らなければ、それが実際に何であるかを区別することはできません。  では、わざわざ確認する必要はあるのでしょうか。  errの意味を正確に知らずとも、ほかの情報を見ることで把握できることがたくさんあるからです。  エントリがたくさんある場合や、普段から目にするエントリとは異なるエントリがある場合には、InterSystemsに問い合わせることもできます。  syslogテーブルのエントリは、必ずしもエラー状態を示すものではないことに注意してください。

Process

これは、syslogテーブルにエントリを書き込んだプロセスのプロセスIDです。  たとえば、スタックしたプロセス、スピンし続けるプロセス、またはデッドプロセスがある場合、syslogテーブルに何か記録されていないかを確認できます。  記録されていれば、プロセスで障害が起きた理由の重要な手がかりになるかもしれません。

Date/Time

これは、エントリが書き込まれた日時です。  問題になった原因の手がかりを得るために、エントリの日時とシステムイベントの日時を相関することは非常に重要です。

ModとLine

Modは特定のCファイルに対応しており、Lineは、そのエントリをsyslogテーブルに書き込んだファイルの行番号です。  カーネルコードにアクセスできるInterSystemsの従業員のみが、これを検索できます。  このコードを調べるだけで、エントリに何が記録されたのかを正確に知ることができます。

Routine

syslogテーブルにエントリが書き込まれたときにプロセスが実行していたタグ、オフセット、およびルーチンです。  何が起こっているのかを理解する上で非常に役立ちます。

Namespace

これは、プロセスが実行していたネームスペースです。

では、err 9がsyslogテーブルに書き込まれた理由をどのようにして知ることができますか?

まず、示されているルーチンを確認します。  私の^systestルーチンは次のようになっています。

systest  ;test for syslog post
         s file="/home/testfile"
         o file:10
         u file w "hello world"
         c file
         q

syslogエントリでは、エントリが書き込まれた時に実行していたものはsystest+3だったと示されています。  この行は次のようになっています。

u file w "hello world"

プロセスがファイルに書き込もうとしていたため、これは実際にOSレベルのエラーである可能性があります。そこで、/usr/include/errno.hで9を探すと、次のようになっています。

#define EBADF   9       /* Bad file descriptor                  */

9はファイル関連であり、示されたコードの行はファイルに書き込もうとしていたため、これは実際にOSエラーコードであると推測するのが合理的です。 

何がエラーになっているのかわかりますか?

これを解決するために、まず、/homeディレクトリとtestfileファイルの権限を確認しました。  両方とも777となっていたため、ファイルを開いて書き込むのは可能なはずです。  そこでコードをよく見ると、エラーに気づきました。  10秒のタイムアウトの前にコロンを2つ付けていなかったこと、そしてOpenコマンドにはパラメーターを使用していなかったことに気づいたのです。  以下は更新したルーチンです。実際にエラーなしで終了し、ファイルに書き込みます。

systest  ;test for syslog post
         s file="/scratch1/yrockstr/systest/testfile"
         o file:"WNSE":10
         u file w "hello world"
         c file
         q

最後に

syslogテーブルは、正しく使用すればデバッグに役立つ貴重なツールです。  使用するときは、次の点に注意してください。

  1. errは必ずしもオペレーティングシステムのエラーとは限りません。ログに記録できるものは何でも記録されます。  記録された内容については、InterSystemsにお問い合わせください。
  2. ログに記録されているその他の情報を使用して、何が起きているのかを判断します。  エラーと組み合わされたCOSコードの行から、それがOSのエラーであるかどうかについて、合理的に推測することができます。
  3. 解決できない問題がある場合は、syslogテーブルを確認しましょう。手がかりが見つかるかもしれません。
  4. Date/Time、エントリ数、および合計発生数を使って、syslogテーブルのサイズを増やす必要があるかどうかを判断します。
  5. システムがsyslogテーブルに何を記録しているのかを把握しておきましょう。エントリに何らかの変更があったり、新しいエントリや異なるエントリが記録されたことに気づくことができます。
  6. syslogテーブルのエントリは、必ずしも問題を示すものではありません。
0
0 566
記事 Toshihiko Minamoto · 6月 17, 2021 4m read

注記(2019年6月): 多くの変更がありました。最新の情報についてはこちらをご覧ください。 注記(2018年9月): この記事が最初に投稿されて以来、多くの変更がありました。Dockerコンテナバージョンを使用することをお勧めします。コンテナとして実行するためのプロジェクトと説明は、以前と同じGitHubの公開場所に残っていますので、ダウンロードして実行し、必要に応じて変更してください。

お客様と共にパフォーマンスレビュー、キャパシティ計画、およびトラブルシューティングに取り組む際、pButtonsから、Cachéとオペレーティングシステムのメトリクスを解凍してレビューすることがよくあります。 しばらく前、HTMLファイルを参照して、グラフ化するセクションを切り取ってExcelに貼り付ける代わりに、UNIXシェル、Perl、およAWKスクリプトで書かれたpButtonsメトリクスのアンパックユーティリティを使用する記事を投稿しました。 これは、時間を節約できる便利な手法ではありますが、内容が全部網羅されているわけではありません。 また、メトリクスを素早く確認して、レポートに含められるように、メトリクスを自動的にグラフ化するスクリプトも使用しています。 ただし、これらのグラフ作成スクリプトの管理は簡単に行えるわけではなく、iostatやwindows perfmonのディスクのリストなど、サイト固有の構成が必要な場合は特に複雑になるため、グラフ作成ユーティリティを一般に公開したことはありませんでしたが、 現在では、もっと単純なソリューションがあります。

この幸運は偶然にも、私がお客様の現場でシステムパフォーマンスを確認していたときに訪れました。Fabianが便利なPythonモジュールを使ってグラフ作成しているのを見せてくれたのです。 この方法は、私が使っていたスクリプトよりもはるかに柔軟で、管理しやすいソリューションです。 Pythonモジュールを統合してファイルの管理とグラフ作成を簡単に行え、さらにインタラクティブHTMLで共有できるため、出力にさらに高い使用効果があります。 そこで、Fabianの記事を基に、お客様のpButtonsファイルを素早く簡単に抽出して、いくつかの形式でグラフ化する__Yape__を作成しました。 このプロジェクトは、GitHubで公開しているため、ダウンロードして実行し、必要であれば変更することができます。

概要

現時点では、このプロセスには_2つ_のステップがあります。

ステップ 1. extract_pButtons.py

関心のあるセクションをpButtonsから抽出し、Excelで開くか、graph_pButtons.pyでグラフ作成の処理を行えるように、.csvファイルに書き込みます。

ステップ 2. graph_pButtons.py

ステップ1で作成したファイルをグラフ化します。 現在、出力は折れ線グラフか点グラフで、.pngか、拡大縮小や印刷のオプションが備わったインタラクティブな .html形式で出力されます。

2つのPythonスクリプトのセットアップと実行方法については、GitHubの_Readme.md_に説明されています。それが最新のリファレンスです。

その他の注意事項

例として、出力ディレクトリと入力ディレクトリにプレフィックスを追加するオプションを使用すると、ある一式(weeksなど)のpButtons HTMLファイルのあるディレクトリ内を簡単にナビゲートし、pButtonsファイルごとに個別のディレクトリに出力することができます。

for i in `ls *.html`; do ./extract_pButtons.py $i -p ${i}_; done

for i in `ls *.html`; do ./graph_pButtons.py ./${i}_metrics -p ${i}_; done

Cachéのキャパシティ計画とパフォーマンスに関する連載を続ける間、短期的には、これらのユーティリティを使ってグラフを作成しようと思います。

OSXではテストしましたが、Windowsではテストしていません。 WindowsにもPyathonをインストールして実行できるはずですが、Windowsでの使用体験について、フィードバックを書き込んでください。 おそらく、ファイルパスのスラッシュなどを変更する必要があると思います。

注意: 数週間前まで、Pythonを使ったことはありませんでした。Pythonエキスパートの方は、コードの書き方がベストプラクティスではないことに気づくことがあるかもしれません。 スクリプトはほぼ毎日使用しているので、今後も精進していきます。 私のPythonスキルが向上することを期待してはいますが、修正が必要な点があれば、遠慮なくご教授ください!

スクリプトが皆さんのお役に立てられたなら、そのことをお知らせください。また、定期的に記事をご覧の上、新機能や更新情報を入手してください。

0
0 158
記事 Megumi Kakechi · 6月 16, 2021 2m read

これは InterSystems FAQ サイトの記事です。

InterSystems製品のシステムモニタが色々なリソースの使用状況を監視しています。

そしてその使用状況に応じてアラートやワーニング情報をコンソールログ(message.log/cconsole.log)に出力します。

アラート情報が表示するCPUのリソースについては、以下のものが定義されています。

0
0 361
記事 Toshihiko Minamoto · 5月 6, 2021 4m read

中間データベースを使用して、Grafana と IRIS(または Cache/Ensemble)を使用する方法を説明した非常に有益な記事がコミュニティにいくつか掲載されています。

しかし私は、IRIS 構造に直接アクセスしたいと考えていました。 特にこのリンクで説明しているように、SQL でアクセス可能なCaché履歴モニターのデータにアクセスしたかったのです。

https://community.intersystems.com/post/apm-using-cach%C3%A9-history-monitor

また、データをいじりたくもありませんでした。

必要とするデータを返すクラスクエリはすでにあったので、それらを、JSON を返す REST クラスに埋め込むだけで良かったからです。 クラス Grafana.MonitorData はまだ含めていません。それでなければならないという理由があるわけではなかったためですが、要望があれば、含めることは可能です。

難しい点は 2 つだけでした。 1 つは、各ポイントで、現地時間と UTC 時間を確実に調整することでした。 もう 1 つは、Grafana は「.25」のようにゼロを省略した小数点数値を好まないため、JavaScript エラー「t.dataList.map is not a function 」が発生していたことです。$FN(tValue,,4) を使用した行があるのはそのためです。

原理を明確化するために、私の運用コードを単純化しました。 GitHub に置くことはできますが、とても単純なので、置かないかもしれません。

 
ソースコード

 

Class Grafana.SYSHistory Extends %CSP.REST
{
XData UrlMap
{
<Routes>
    <Route Url="/" Method="GET" Call="testAvailability" Cors="true" />
    <Route Url="/search" Method="POST" Call="metricFindQuery" Cors="true" />
    <Route Url="/query" Method="POST" Call="query" Cors="true" />
    </Routes>
}
ClassMethod testAvailability() As %Status
{
    write "ok"
    quit $$$OK
}
/// このメソッドは利用可能なメトリクスのリストを返します.
ClassMethod metricFindQuery() As %Status
{
    do ##class(Grafana.MonitorData).GetSupportedMetrics(.metrics)
    "["
    set sub=""
    set firsttime=1
    do 
      set sub=$o(metrics(sub))
      quit:sub=""
    if firsttime=0 ","
     set firsttime=0
     """",sub,"""" 
while sub'=""
write "]"
quit $$$OK
}
/// Grafana のデータ形式 - http://docs.grafana.org/plugins/developing/datasources/
ClassMethod query() As %Status
{
set obj = {}.%FromJSON(%request.Content)
 if obj="" {
 write "no object found"
 quit $$$OK   
    
   }
    
    set iter=obj.targets.%GetIterator()
    set tMetrics=0
    while iter.%GetNext(.key,.value) {
    set tMetrics=tMetrics+1
    set tMetrics(tMetrics) = value.target
    }
    set from = obj.range.from
    set to = obj.range.to
#define classname 1
#define queryname 2
set (className,queryName)=""
//どのクエリからどのクラスにもアクセスできないように、クラスをハードコードして、'NamedQuery' アイテムを使用します...
set className="Grafana.MonitorData"
set queryName="SysMonHistorySummary"
write "["
for i=1:1:tMetrics {
if i>1 ","
"{""target"":"""_tMetrics(i)_""",""datapoints"":["
do ..ExportJSON(className,queryName,from,to,tMetrics(i))
write "]}"
}
write "]"
quit $$$OK
}
/// 実行するクエリを判定する className と QueryName。
/// from と to は現地時間で、%Date (a.k.k. $horolog) 形式です。
/// クエリはメトリクスの値を返す必要があります。 このコードは、値が Avg_Metric と RunDate
/// として返されることを前提としていますが、それは変更可能です。


ClassMethod ExportJSON(className As %String, queryName As %String, from, to, pMetric As %String) As %Status
{
if className="" quit $$$OK
if queryName="" quit $$$OK
set rs=##class(%ResultSet).%New(className_":"_queryName)
if rs="" quit $$$ERROR($$$QueryDoesNotExist,className_":"_queryName)
// これは param 情報としてのみ使用
set sc=$classmethod(className,queryName_"GetInfo",.colinfo,.paraminfo,.idinfo,.QHandle,0,.extinfo)
  
//リクエストにはクエリのパラメーターに一致する名前のあるデータが含まれる必要があります。
//日付と時刻のパラメーターを文字列から $h に変換します。
set from=$e(from,1,19)
set to=$e(to,1,19)
set RunDateUTCFromH=$zdth(from,3)
set RunDateFromH=$zdth(RunDateUTCFromH,-3)
set RunDateUTCToH=$zdth(to,3)
set RunDateToH=$zdth(RunDateUTCToH,-3)
set tSc=rs.Execute(RunDateFromH,RunDateToH,"live",pMetric) //param(1),param(2))
if $$$ISERR(tSc) quit tSc
set rowcnt=0
while rs.Next() {
set rowcnt=rowcnt+1
if rowcnt>1 write ","
write "["
set tRunDate=rs.Data("RunDate")
set tUtcRunDate=$zdt(tRunDate,-3)
set tValue=rs.Data("Avg_Metric")
set tPosixTime=##class(%Library.PosixTime).OdbcToLogical($zdt(tUtcRunDate,3,3))
set tUnixTime=##class(%Library.PosixTime).LogicalToUnixTime(tPosixTime)_"000"
write $fn(tValue,,4),",",tUnixTime
write "]"
}
quit $$$OK
}
}

0
0 224
記事 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
記事 Tomoko Furuzono · 9月 17, 2020 17m read

Caché 2017以降のSQLエンジンには新しい統計一式が含まれています。 これらの統計は、クエリの実行回数とその実行所要時間を記録します。

これは、多くのSQLステートメントを含むアプリケーションのパフォーマンスを監視する人や最適化を試みる人にとっては宝物のような機能ですが、一部の人々が望むほどデータにアクセスするのは簡単ではありません。

この記事と関連するサンプルコードでは、このような情報の使用方法と、日次統計の概要を定期的に抽出してアプリケーションのSQLパフォーマンス履歴記録を保持する方法について説明します。
※詳細については、下記ドキュメントページもご参考になさってください。

https://docs.intersystems.com/iris20201/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_sqlstmts

記録内容

SQLステートメントが実行されるたびに、所要時間が記録されます。 この処理は非常に軽量であり、オフにすることはできません。 コストを最小限に抑えるため、統計はメモリに保持されてから定期的にディスクに書き込まれます。 このデータには当日にクエリが実行された回数と、その平均所要時間と合計所要時間が含まれます。

0
0 355
記事 Toshihiko Minamoto · 8月 13, 2020 3m read

皆さん、こんにちは。

InterSystems System Alerting and Monitoring (SAM)をご存知でしょうか。InterSystems IRIS 2020.1以降に対応し、IRISやそのアプリケーションの監視を行うソリューションです。といってもシステム監視を行うPrometheus、アラートを管理するAlertManager、ダッシュボードとしてグラフ等を表示させるGrafanaなどを組み合わせたものですが、IRISの利用者に合わせて設定しやすくなっています。

なお、これらのコンポーネントはDockerコンテナを使用しますので、Docker(19.3.098以降)ならびにDocker compose(1.25以降)をインストールいただく必要があります。

IRISの監視APIについてはこちらをご覧ください。

インストール手順

1. アプリケーションのインストール

アプリケーション自体はDockerコンテナにて提供されますので、DockerComposeのファイルやシェルスクリプトを以下のGitHubリポジトリからダウンロードします。
https://github.com/intersystems-community/sam

以下のgzipファイルをダウンロードし、展開します。
sam-1.0.0.XXX-unix.tar.gz

1
1 346
記事 Tomohiro Iwamoto · 8月 28, 2020 3m read

本記事について

InterSystems IRISをモニタリングする方法はいくつかあります。

  • SNMP
  • システムモニターとemail通知機能
  • 管理ポータルのシステムダッシュボード
  • Prometheus+Grafanaを使用
  • InterSystems SAM (System Alerting and Monitoring)

本記事では上記に加えてAWSにIRISをデプロイする場合に自然な選択子となりうる方法として、CloudWatchを使用する方法をご紹介します。

AWSネイティブの各種サービスとIRISを連携させる方法の典型のご紹介を兼ねています。

内容は、Open Exchangeで公開されています。日本語のREADMEがありますのでそちらをご覧ください。
README.MDからの引用

InterSystems IRISの各種メトリクスとログをAWS CloudWatchに簡単に公開することができます。
これらメトリクスとログがあれば、IRISのデータをダッシュボードやアラートなどに統合することができます。

0
0 246
記事 Shintaro Kaminaka · 7月 3, 2020 8m read

IRIS 2019.4以降の製品には、Prometheus形式でIRISのメトリックを公開する/api/monitorサービス機能が実装されています。 IRISのメトリックを監視・警告ソリューションの一部として使用したい人にとっては大きなニュースです。 このAPIは、IRISの次期バージョンでリリースされる予定の新しいIRIS System Alerting and Monitoring (SAM) ソリューションのコンポーネントです。 

ただし、IRISインスタンスを監視するためにSAMがこのAPIの計画と実証実験を開始するのを待つ必要はありません。 今後の投稿では利用可能なメトリックとその意味についてさらに掘り下げ、対話型ダッシュボードの例を示します。 しかし、まずは背景の説明といくつかの質問と回答から始めましょう。 

0
0 401
記事 Tomohiro Iwamoto · 6月 5, 2020 14m read

前回の記事では、pButtonsを使って履歴パフォーマンスメトリックを収集する方法を説明しました。 すべてのデータプラットフォームインスタンス(Ensemble、Cachéなど)にはpButtonsがインストールされていることがわかっているため、私はpButtonsを使用する傾向にありますが、 Cachéパフォーマンスメトリックをリアルタイムで収集、処理、表示する方法はほかにもあり、単純な監視や、それよりもさらに重要な、より高度な運用分析とキャパシティプランニングに使用することができます。 データ収集の最も一般的な方法の1つは、SNMP(簡易ネットワーク管理プロトコル)を使用することです。 

SNMPは、Cachéが管理・監視情報をさまざまな管理ツールに提供するための標準的な方法です。 Cachéのオンラインドキュメンテーションには、CachéとSNMP間のインターフェースに関する詳細が説明されています。 SNMPはCachéと「単純に連携」するはずですが、構成にはいくつかの技と罠があります。 私自身、はじめに何度も過ちを繰り返し、InterSystemsの同僚から助けを得ながらCachéをオペレーティングシステムのSNMPマスターエージェントにやっと接続できた経験から、皆さんが同じような困難を避けられるようにこの記事を書くことにしました。 

0
0 352
記事 Toshihiko Minamoto · 5月 10, 2020 11m read

次の手順で、/api/monitor サービスから利用可能なメトリックのサンプル一覧を表示することができます。 

前回の投稿では、IRISのメトリックをPrometheus形式で公開するサービスの概要を説明しました。 この投稿では、コンテナにIRISプレビューリリース2019.4 をセットアップして実行し、メトリックを一覧表示する方法をお伝えします。 

この投稿は、Dockerがインストールされた環境があることを前提としています。 そうでない場合は、今すぐお使いのプラットフォームにインストールしてください :) 

ステップ 1. dockerでIRISプレビューをダウンロードして実行する 

プレビューの配布」のダウンロード手順に従い、プレビューライセンスキーとIRISのDockerイメージをダウンロードします。 この例では、InterSystems IRIS for Health 2019.4を選択しています。 

「機能紹介:Dockerコンテナ内のInterSystems製品について」の指示に従ってください。 すでにコンテナに精通している場合は、「InterSystems IRISのDockerイメージをダウンロードする」というタイトルのセクションに進んでください。 

0
0 251