カスタムメトリクスとは、Pythonエージェントが提供するAPIを利用して、任意のメトリクスを記録することができます。これらは、Web アプリケーションが実装するビジネス機能に関連するメトリクスを記録するために使用されるかもしれませんし、Web アプリケーションのパフォーマンスを評価するために使用される追加のメトリクスかもしれません。
推奨事項: 潜在的なデータ問題を回避するには、カスタムメトリックによって導入される一意のメトリクスの総数を 2000 未満に抑えてください。
重要
カスタムメトリクスを使用する前に、エージェントを初期化してターゲットプロセスと統合する必要があります。手順については、 Pythonエージェントの統合 を参照してください。
カスタムメトリクスのグラフ化
カスタムメトリクスを表示するには、 データを照会して メトリクスを検索し、カスタマイズ可能なチャートを作成します。
プッシュ型とプル型のインターフェース
Pythonエージェントは、カスタムメトリクスを記録する2つの異なる方法を提供します。1つ目はプッシュ型のAPIで、カスタムメトリクスを記録するタイミングを決めることができます。2つ目は、カスタムメトリクスのデータソースを登録し、エージェントが 収穫サイクルごとに1回、メトリクスのためにコードをポーリングするプルスタイルのAPIです 。
プル型APIは、ハーベストサイクルの期間中にレートや利用率のメトリクスを生成する必要がある場合に重要です。これは、ハーベストサイクルの期間を適切に計算することができ、また、ハーベストサイクルに対して1つのメトリックのみが記録されるようにすることができるからです。
単一のメトリックを記録する
1つのカスタムメトリックを記録するために、Pythonエージェントは関数を提供します。
newrelic.agent.record_custom_metric(name, value, application=None)
として、アプリケーションオブジェクトなしで呼び出された場合
newrelic.agent.record_custom_metric('Custom/Value', value)
の場合は、エージェントが監視しているトランザクションのコンテキスト内で呼び出す必要があります。これは、現在のトランザクションが検索され、カスタムメトリクスがそのトランザクションに最初に添付されるためです。
トランザクションがその後無視されるようにマークされていない限り、カスタムメトリクスは、トランザクションが完了したときに、そのトランザクションが報告されているアプリケーションの他のメトリクスと集約されます。
このAPI関数が、監視対象のトランザクションのコンテキスト外、例えばバックグラウンド・スレッド(バックグラウンド・タスクとして追跡されていない)で呼び出された場合、この呼び出しは何もせず、データは破棄されます。このような状況でカスタム・メトリクスを記録するためには、カスタム・メトリクスを記録すべきアプリケーションに対応するアプリケーション・オブジェクトを指定する必要があります。
application = newrelic.agent.register_application()
def report_custom_metrics(): while True: newrelic.agent.record_custom_metric('Custom/Value', value(), application) time.sleep(60.0)
thread = threading.Thread(target=report_custom_metrics)thread.setDaemon(True)thread.start()
現在のトランザクションに対してカスタム・メトリクスを記録する場合(アプリケーション・オブジェクトを提供しない場合)、カスタム・メトリクスは最初にトランザクション・オブジェクトに添付されるため、API呼び出し時にスレッド・ロックは必要ありません。スレッドロックを取得する必要があるのは、トランザクション全体が完了時に記録されているときだけです。これは、トランザクションからのすべてのメトリクスを現在の収穫サイクルのメトリクス・テーブルにマージするために取得する必要があるロックと同じものです。そのため、すでに必要とされているものに加えて、追加のロックは必要ありません。
しかし、APIコールがアプリケーション・オブジェクトに提供されている場合、カスタム・メトリックを記録するためには、コールごとにロックを取得する必要があります。そのため、多数のメトリクスを一度に記録すると、スレッドロックの競合により、過度な影響を受ける可能性があります。
複数のメトリクスを記録
一度に複数の測定値を記録する場合は、スレッドロックの必要性を減らすために、代わりにこの関数を使用することができます。
newrelic.agent.record_custom_metrics(metrics, application=None)
これは、名前と値の引数の代わりに iterable を指定できることを除いて、 record_custom_metric()
呼び出しと同じように機能します。iterable は、ジェネレーター関数を含む、リスト、タプル、またはその他の反復可能なオブジェクトにすることができます。iterable は、カスタム メトリックの名前と値で構成されるタプルを返す必要があります。
import psutilimport os def memory_metrics(): pid = os.getpid() p = psutil.Process(os.getpid()) m = p.get_memory_info()
yield ('Custom/Memory/Physical', float(m.rss)/(1024*1024)) yield ('Custom/Memory/Virtual', float(m.vms)/(1024*1024)) application = newrelic.agent.register_application()
def report_custom_metrics(): while True: newrelic.agent.record_custom_metrics(memory_metrics(), application) time.sleep(60.0)
thread = threading.Thread(target=report_custom_metrics)thread.setDaemon(True)thread.start()
アプリケーション・オブジェクトで使用すると、カスタム・メトリクスをどれだけ多く記録しても、スレッド・ロックは呼び出しごとに1回だけ実行する必要があります。
カスタムメトリクスのネーミング
Python エージェントによって報告されるすべてのカスタム メトリックは、接頭辞Custom/
で始まる必要があります。通常、この後にカテゴリ名とラベル セグメントが続きます。Custom/
メトリックが使用されていない場合、メトリックおよびイベントでカスタム メトリックを選択できない可能性があります。
事前に集計されたメトリクス
利用可能なメトリクスのセットに反復可能なものを渡してメトリクスのセットを記録する場合、同じ名前のメトリクスが複数回現れることがあります。このような場合、エージェントは個別の値を1つのサンプルに集約します。
可能ではあるが、このようにして単一のメトリクスのために個々の生のサンプルを保持し、後に渡すことが現実的でない場合、メトリクスのソースは代わりにメトリクスを事前に集約し、結果として集約されたデータサンプルを提供することができる。
そのため、値は数値ではなく、ディクショナリーを渡すことになります。辞書内のフィールドは以下の通りです。
count
total
min
max
sum_of_squares
単一のメトリックに対するアグリゲーションを実行するために使用できるヘルパークラスの実装は次のとおりです。
class Stats(dict):
def __init__(self, count=0, total=0.0, min=0.0, max=0.0, sum_of_squares=0.0): self.count = count self.total = total self.min = min self.max = max self.sum_of_squares = sum_of_squares
def __setattr__(self, name, value): self[name] = value
def __getattr__(self, name): return self[name]
def merge_stats(self, other): self.total += other.total self.min = self.count and min(self.min, other.min) or other.min self.max = max(self.max, other.max) self.sum_of_squares += other.sum_of_squares self.count += other.count
def merge_value(self, value): self.total += value self.min = self.count and min(self.min, value) or value self.max = max(self.max, value) self.sum_of_squares += value <DNT>** 2 self.count += 1
このクラスは、それ自体が辞書であるため、そのインスタンスを直接値として渡すことができます。
これは、次のように使うことができます。
application = newrelic.agent.register_application()
def sample_value(): return ...
def report_custom_metrics(): count = 0 stats = Stats()
while True: count += 1
stats.merge_value(sample_value())
if count % 60 == 0: newrelic.agent.record_custom_metric('Custom/Value', stats, application) stats = Stats()
time.sleep(1.0)
thread = threading.Thread(target=report_custom_metrics)thread.setDaemon(True)thread.start()
カスタムメトリックデータソース
record_custom_metric()
およびrecord_custom_metrics()
API 呼び出しでは、カスタム メトリックをエージェントにプッシュするために、引き続き明示的な操作が必要です。
エージェントへのデータのプッシュは、特にバックグラウンドのスレッドから60秒間隔で行われる場合には、問題が生じることがあります。これは、データのプッシュが、エージェントがデータコレクタにデータを報告するタイミングと正確に同期しない可能性があるためです。
バックグラウンド・スレッドが60秒間にわたってメトリクスを事前に集計し、それを記録していた場合、それがエージェントがデータを報告する時間に近い場合、エージェントがデータを報告する直前または直後に発生する可能性があります。このように時間的な同期が取れていないと、ある収穫サイクルではそのサンプルのメトリクスが報告されず、次の収穫サイクルでは2つ報告されることになります。
この問題を解決するには、エージェントがデータを報告するプロセスの一部として、カスタムメトリクスのプロデューサーからカスタムメトリクスを引き出して、直ちに報告されるようにし、収穫サイクルと同期させることです。
このようなプル型APIにおけるメトリックのソースをメトリックデータソースと呼ぶ。
データソースの登録
メトリックデータソースを登録するためのAPI関数は
newrelic.agent.register_data_source(source, application=None, name=None, settings=None, **</DNT>properties)
カスタムメトリクスの作成方法にはさまざまな要件があるため、データソースの実装にはさまざまな方法があります。
最もシンプルなタイプのデータソースは、ゲージメトリックを提供するものです。つまり、ある特定の時点での値が重要であり、過去に起こったことは重要ではないというものです。
import psutilimport os
@newrelic.agent.data_source_generator(name='Memory Usage')def memory_metrics(): pid = os.getpid() p = psutil.Process(os.getpid()) m = p.get_memory_info() yield ('Custom/Memory/Physical', float(m.rss)/(1024*1024)) yield ('Custom/Memory/Virtual', float(m.vms)/(1024*1024)) newrelic.agent.register_data_source(memory_metrics)
ここで使われているデコレーターは
newrelic.agent.data_source_generator(name=None, **properties)
これは特に、ジェネレータ関数や、呼び出されたときに反復可能な値を返す関数をラップするためのものです。
データソース登録時の名前は任意です。これは主に、エラーをログに記録するときに、メッセージがデータ ソースによりわかりやすい名前を付けることができるようにするために存在します。name がregister_data_source()
に渡されない場合、デコレータを使用して実際のデータ ソースに関連付けられた名前が代わりに使用されるか、データ ソース自体に名前が付けられていない場合は関数の名前が使用されます。
データソースの登録時にアプリケーション・オブジェクトが提供されない場合、データソースは、そのプロセスでエージェントがデータを報告するすべてのアプリケーションに自動的に関連付けられます。アプリケーションが提供された場合、データソースはその特定のアプリケーションにのみ関連付けられます。
データ ソースがアプリケーションに対して明示的に登録されているか、すべてのアプリケーションに適用されているかに関係なく、最初にエージェントをそのアプリケーションに登録する必要があります。これは通常、監視されていた既存の Web アプリケーション プロセスでデータ ソースを使用している場合に発生します。ただし、スタンドアロン プログラムでデータ ソースを使用してカスタム メトリックのみを報告している場合でも、必要に応じて API 呼び出しregister_application()
を使用して、データを収集する前にアプリケーションのエージェントの登録を強制する必要があります。 .
データソースの初期化
デコレーターはデータ・ソースに名前を付ける機能を提供しますが、デコレーターのより重要な理由は、データ・ソースを実行するための一連の設定手順の複雑さを隠すことができるからです。これらのステップの順序は
- データソースは初期化され、特定の方法で実行するように設定するために渡される構成を保持する辞書があります。
- 初期化されると、データソースは、データソースを説明するプロパティの辞書を返します。これには、データソースプロバイダの特定のインスタンスを作成するためのファクトリ関数への参照が含まれます。
- データソースプロバイダーのインスタンスは、ファクトリーを呼び出すことで、特定のコンシューマー(アプリケーション)用に作成されます。ファクトリー関数には、コンシューマーの名前を含む、実行中の環境を記述したディクショナリーが渡されます。
上の例をデコレーターに頼らないように書き換えると、次のようになります。
import osimport psutil def memory_metrics_data_source(settings): def memory_metrics(): pid = os.getpid() p = psutil.Process(os.getpid()) m = p.get_memory_info() yield ('Custom/Memory/Physical', float(m.rss)/(1024*1024)) yield ('Custom/Memory/Virtual', float(m.vms)/(1024*1024))
def memory_metrics_factory(environ): return memory_metrics properties = {} properties['name'] = 'Memory Usage' properties['factory'] = memory_metrics_factory return properties newrelic.agent.register_data_source(memory_metrics_data_source)
より複雑な基本プロトコルの目的は、データソースを適切に初期化し、その構成と消費者の仕様に基づいてカスタマイズするための十分なフックポイントを提供することです。
データソースのインスタンス
前述の例では、最後に生成されたことを気にしないゲージメトリクスが返されていたため、これ以上何もする必要がありませんでした。メトリクスが時間の経過とともに起こる何かを反映しており、そのために何らかの状態を保持する必要がある場合、データソースのインスタンスを作成できる機能が必要になります。
そのため、ファクトリー機能では、メトリクスが報告されるアプリケーションごとにデータソースのインスタンスを作成することができます。
アプリケーションごとに収穫サイクルの開始時間と終了時間が異なる可能性があるため、プロセスごとではなく、アプリケーションごとにデータソースのインスタンスを1つ用意しています。このシナリオでは、プロセスごとに1つしかなく、メトリックが収穫サイクルの持続時間と関連していた場合、結果として得られるメトリックは各アプリケーションに対して正しくないだろう。そのため、データソースインスタンスには、アプリケーション固有の機能が用意されています。
上記のようにネストされた関数を使用して、状態を維持する必要のあるデータソースは次のように書くことができます。
import osimport timeimport multiprocessing @newrelic.agent.data_source_factory(name='CPU Usage')def cpu_metrics_data_source(settings, environ): state = {} state['last_timestamp'] = time.time() state['times'] = os.times()
def cpu_metrics(): now = time.time() new_times = os.times() elapsed_time = now - state['last_timestamp'] user_time = new_times[0] - state['times'][0] utilization = user_time / (elapsed_time*multiprocessing.cpu_count()) state['last_timestamp'] = now state['times'] = new_times
yield ('Custom/CPU/User Time', user_time) yield ('Custom/CPU/User/Utilization', utilization)
return cpu_metrics newrelic.agent.register_data_source(cpu_metrics_data_source)
ここで使われているデコレーターは
newrelic.agent.data_source_factory(name=None, **properties)
このケースでは、デコレーターはファクトリー関数をラップしています。デコレーターは必要に応じてデータソースのプロパティを自動的に返すので、ファクトリーは設定と使用される環境の説明の両方を受け取ります。
入れ子になった関数を使用するのはちょっとしたマジックで、外側の関数のスタックにある辞書を使って状態を保持するコードが必要になります。もうひとつの方法は、データソースを実際のクラスとして実装し、デコレータをそのクラスに適用することです。
import osimport timeimport multiprocessing @newrelic.agent.data_source_factory(name='CPU Usage')class CPUMetricsDataSource(object):
def __init__(self, settings, environ): self.last_timestamp = time.time() self.times = os.times()
def __call__(self): now = time.time() new_times = os.times() elapsed_time = now - self.last_timestamp user_time = new_times[0] - self.times[0] utilization = user_time / (elapsed_time*multiprocessing.cpu_count()) self.last_timestamp = now self.times = new_times
yield ('Custom/CPU/User Time', user_time) yield ('Custom/CPU/User/Utilization', utilization)
newrelic.agent.register_data_source(CPUMetricsDataSource)
データソースのライフサイクル
データソースはいつでもメトリクスを生成できますが、エージェント自体は常にアプリケーションのメトリクスを報告しているわけではありません。具体的には、エージェントが特定のアプリケーションのデータコレクタに自分自身を登録することができた場合にのみ、メトリクスの収集を開始し、報告することになります。
この違いは、期間に基づいてメトリクスを生成するデータソースにとって重要である。データソースによって生成されるメトリクスは、登録が発生した時点までの期間、またはエージェントによってメトリクスが報告された最後の時点までの期間をカバーするようにすることが必要です。これが行われない場合、報告されたメトリクスは整合しないため、Webトランザクションやバックグラウンドタスクの追跡によるメトリクスと適切に相関することを保証することはできません。
このため、データソースのファクトリーは、アプリケーションの登録が完了し、メトリクスの収集が開始されたときにのみ、データソースのインスタンスを作成するために呼び出されます。これにより、参照するタイムスタンプが正しいものになります。
サーバー側の設定変更によるサーバー側の強制再起動や、データコレクタへのデータ報告が連続して失敗したことなどにより、特定のアプリケーションに対するエージェントの実行が終了すると、データソースは削除されます。データソースの新しいインスタンスは、エージェントがアプリケーションのために再び自分自身を再登録することができたときに作成されます。
この場合のデータ ソースの正しいクリーンアップは、データ ソース オブジェクトが削除されたときにデータ ソース オブジェクトが迅速に破棄されるかどうかによって異なります。オブジェクト参照カウント サイクルのため、これに依存することはできません。また、データ ソースがクリーンアップ アクションをトリガーするために__del__()
メソッドを追加する必要がないようにすることも望ましいです。これは、 __del__()
メソッドがオブジェクトの迅速な破棄を実際に防止する方法で導入される問題のためです。
このため、データ ソースがセットアップとシャットダウンをより詳細に制御する必要がある場合、たとえばstart()
メモリ内に永続的に保持され、ドロップされないようにする必要がある場合、メトリクスの計算をstop()
停止できます。クラスインスタンスとして実装。
import osimport timeimport multiprocessing
@newrelic.agent.data_source_factory(name='CPU Usage')class CPUMetricsDataSource(object):
def __init__(self, settings, environ): self.last_timestamp = None self.times = None def start(self): self.last_timestamp = time.time() self.times = os.times() def stop(self): self.last_timestamp = None self.times = None
def __call__(self): if self.times is None: return
now = time.time() new_times = os.times() elapsed_time = now - self.last_timestamp user_time = new_times[0] - self.times[0] utilization = user_time / (elapsed_time*multiprocessing.cpu_count()) self.last_timestamp = now self.times = new_times
yield ('CPU/User Time', user_time) yield ('CPU/User/Utilization', utilization)
newrelic.agent.register_data_source(CPUMetricsDataSource)
start()
メソッドとstop()
メソッドを定義すると、データ ソースのインスタンスはエージェント実行の終了時に破棄されず、保持されます。この時点でエージェントは、データ ソース自体がメトリクスの集計の一時停止を処理し、蓄積されたメトリクスを削除し、エージェントがアプリケーションをデータ コレクターに再登録してstart()
を再度呼び出すときに、それを確実に行うことを期待しています。メトリクスの追跡が再開されます。
データソースの設定
データ ソースは、常に 1 つの特定の情報ソースにバインドされているとは限りません。メトリクスの生成元であるさまざまな基礎となる情報ソースに対してデータ ソースを登録する必要がある場合があります。この場合、 register_data_source()
関数を使用してデータ ソースを登録するときに個別の設定を渡すことができます。データ ファクトリを使用する場合、これらの設定は、データ ソースの初期化時に使用できるようになります。
@newrelic.agent.data_source_factory()class HostMonitorDataSource(object): def __init__(self, settings, environ): self.hostname = settings['hostname']
def __call__(self): ... newrelic.agent.register_data_source(HostMonitorDataSource, name='Host Monitor (host-1)', settings=dict(hostname='host-1'))newrelic.agent.register_data_source(HostMonitorDataSource, name='Host Monitor (host-2)', settings=dict(hostname='host-2'))
設定のプロビジョニングがオプションの場合、データ ソースはsettings
オプションがNone
でない場合にのみ設定へのアクセスを試行する必要があります。辞書が提供されている場合でも、辞書にない設定にも対処する必要があります。
設定ファイルからのセットアップ
ここの例ではregister_data_source()
API 呼び出しの使用を示しましたが、これはデータ ソースを登録する通常の方法ではありません。これは、データ ソースのモジュールをインポートして登録するためにアプリケーションを変更する必要があるため、推奨される方法ではありません。
代わりに、データ ソースを定義して既存の監視対象 Web アプリケーションに統合するための主な方法は、それらをエージェント構成ファイルにリストすることです。これには、プレフィックスdata-source:
を持つ各データ ソースのエージェント構成ファイルにセクションを追加する必要があります。
[data-source:process-info]enabled = truefunction = samplers.process_info:process_info_data_source
エージェント構成ファイルからデータ ソースを登録する場合、アプリケーション コードまたはデータ ソースを定義するモジュールで発生するregister_data_source()
関数を使用して、同じデータ ソースを個別に登録しないでください。存在する場合、データ ソースの 2 つのインスタンスが登録されることになります。
データ ソースに特定の設定を提供する必要がある場合は、エージェント構成ファイルに別のセクションを作成し、データ ソース構成のsettings
値でセクション名を参照することで実行できます。
[data-source:host-monitor]enabled = truefunction = samplers.process_info:process_info_data_sourcename = Host Monitor (host-1)settings = host-monitor:host-1
[host-monitor:host-1]hostname = host-1
構成ファイルを介して提供されるデータ ソース設定は常に文字列値として渡されるため、アプリケーション コードでregister_data_source()
を使用してデータ ソースを登録し、設定を明示的に提供する場合でも、文字列を値の設定に使用することをお勧めします。その後、データ ソースは、数値や値のリストなどの別の型への変換を処理する必要があります。