Les métriques personnalisées vous permettent d'enregistrer des métriques arbitraires à l'aide d'API fournies par l'agent Python. Ceux-ci peuvent être utilisés pour enregistrer des mesures liées aux fonctions métier implémentées par votre application Web, ou peuvent être des mesures supplémentaires utilisées pour évaluer les performances de l'application Web.
Recommandation : Pour éviter d'éventuels problèmes de données, maintenez le nombre total de métriques uniques introduites par les métriques personnalisées sous 2 000.
Important
Avant d’utiliser des métriques personnalisées, vous devez initialiser l’agent et l’intégrer au processus cible. Pour obtenir des instructions, consultez Intégration de l'agentPython .
Cartographie métriques personnalisée
Pour afficher des métriques personnalisées, requêtez vos données pour rechercher des métriques et créer des graphiques personnalisables.
Interfaces push versus pull
L'agent Python fournit deux manières différentes d'enregistrer des métriques personnalisées. La première est une API de type push où vous pouvez décider quand enregistrer des métriques personnalisées. La seconde est une API de style pull dans laquelle vous enregistrez une source de données de mesures personnalisées et l'agent interroge votre code pour les mesures une fois par cycle de collecte.
L'API de type pull est importante lorsque vous devez générer des mesures de taux ou d'utilisation sur la période du cycle de collecte. Ceci est dû au fait que vous pouvez calculer correctement la durée du cycle de collecte et également garantir qu'une seule métrique est enregistrée pour le cycle de collecte.
Enregistrement d'une seule métrique
Pour enregistrer une seule métrique personnalisée, l'agent Python propose la fonction :
newrelic.agent.record_custom_metric(name, value, application=None)
Lorsqu'il est appelé sans objet d'application comme
newrelic.agent.record_custom_metric('Custom/Value', value)
il doit alors être appelé dans le contexte d'une transaction monitorée par l'agent. Cela est dû au fait que la transaction en cours sera recherchée et que les métriques personnalisées seront initialement attachées à cette transaction.
Tant que la transaction n'est pas ultérieurement marquée comme devant être ignorée, les métriques personnalisées seront alors agrégées avec d'autres métriques pour l'application à laquelle la transaction est signalée, lorsque la transaction est terminée.
Si cette fonction API est appelée en dehors du contexte d'une transaction de monitoring, comme dans un thread d'arrière-plan (qui n'est pas suivi en tant que tâche d'arrière-plan), alors l'appel ne fait rien et les données sont supprimées. Afin de pouvoir enregistrer des métriques personnalisées dans une telle situation, il est nécessaire de fournir l'objet d'application correspondant à l'application par rapport à laquelle les métriques personnalisées doivent être enregistrées.
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()
Dans le cas de l'enregistrement de métriques personnalisées par rapport à la transaction en cours (en ne fournissant pas d'objet d'application), aucun verrouillage de thread n'est requis au moment de l'appel d'API, car les métriques personnalisées seront initialement attachées à l'objet de transaction. C'est seulement lorsque l'ensemble de la transaction est enregistré à la fin qu'un verrouillage de thread doit être acquis. Il s'agit du même verrou que celui qui doit être acquis pour fusionner toutes les métriques de la transaction avec la table métrique pour le cycle de collecte en cours. Ainsi, aucun verrouillage supplémentaire n’est requis en plus de ce qui est déjà requis.
Cependant, lorsque l'appel d'API est fourni à l'objet d'application, il est nécessaire d'acquérir un verrou pour chaque appel afin d'enregistrer des métriques personnalisées. L'enregistrement des métriques une par une de cette manière pour un grand nombre de métriques peut donc avoir des effets indésirables en raison du conflit de verrouillage des threads.
Enregistrement de plusieurs métriques
Si vous enregistrez plusieurs métriques en une seule fois, pour réduire le besoin de verrouillage de thread, vous pouvez utiliser à la place la fonction :
newrelic.agent.record_custom_metrics(metrics, application=None)
Cela fonctionne de la même manière que l'appel record_custom_metric()
, sauf qu'un itérable peut être fourni à la place des arguments nom et valeur. L'itérable peut être une liste, un uplet ou un autre objet itérable, y compris une fonction génératrice. L'itérable doit renvoyer un uplet composé du nom et de la valeur des métriques personnalisées.
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()
Lorsqu'il est utilisé avec un objet d'application, quel que soit le nombre de métriques personnalisées enregistrées, le verrouillage des threads n'aura besoin d'être effectué qu'une seule fois pour chaque appel.
Dénomination des métriques personnalisées
Toutes les métriques personnalisées signalées par l'agent Python doivent commencer par le préfixe Custom/
. Ceci serait généralement suivi d'un nom de catégorie et d'un segment d'étiquette. Si la métrique Custom/
n'est pas utilisée, alors les métriques personnalisées peuvent ne pas être disponibles pour la sélection dans métriques et événement.
Pré-métrique agrégée
Lors de l'enregistrement d'un ensemble de métriques en passant un itérable sur l'ensemble des métriques disponibles, la même métrique nommée peut apparaître plusieurs fois. Dans cette situation, l’agent regrouperait alors les valeurs individuelles en un seul échantillon.
Bien que cela soit possible, si la conservation puis la transmission ultérieure de tous les échantillons bruts individuels pour une seule métrique de cette manière n'est pas pratique, alors la source des métriques peut à la place pré-agrégater les métriques et fournir l'échantillon de données agrégées résultant.
Au lieu que la valeur soit une valeur numérique, un dictionnaire serait transmis pour la valeur. Les champs du dictionnaire seraient :
count
total
min
max
sum_of_squares
Une implémentation d'une classe d'assistance que vous pouvez utiliser pour effectuer une agrégation pour une seule métrique est :
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
Cette classe est elle-même un dictionnaire et une instance de celle-ci peut donc être transmise directement comme valeur.
Cela pourrait alors être utilisé comme :
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()
Sources de données métriques personnalisées
Les appels d'API record_custom_metric()
et record_custom_metrics()
nécessitent toujours une action explicite de votre part pour transmettre des mesures personnalisées à l'agent.
Cependant, l'envoi de données à l'agent, en particulier s'il est effectué à partir d'un thread d'arrière-plan et sur un intervalle de 60 secondes, peut être problématique. Cela est dû au fait que lorsque les données sont transmises, elles peuvent ne pas être synchronisées précisément avec le moment où l'agent renvoie les données au collecteur de données.
Si un thread d'arrière-plan a été pré-agrégé sur une période de 60 secondes, puis enregistré, si cela tombe à proximité du moment où l'agent rapporte les données, cela peut se produire juste avant ou juste après que l'agent ait rapporté les données. Ce manque de synchronisation dans le temps pourrait donc avoir pour conséquence qu'aucune mesure pour cet échantillon ne soit rapportée dans un cycle de collecte et deux dans le suivant, alors que l'intention serait qu'il y en ait une par cycle de collecte.
La solution à ce problème est que l'agent récupère les métriques personnalisées auprès du producteur des métriques dans le cadre du processus de reporting des données pour garantir qu'elles seront signalées immédiatement et synchronisées avec le cycle de collecte.
La source de ces métriques dans cette API de type pull est appelée source de données métriques.
Enregistrement d'une source de données
La fonction API pour enregistrer une source de données métriques est :
newrelic.agent.register_data_source(source, application=None, name=None, settings=None, **</DNT>properties)
En raison des exigences variables concernant la manière dont les métriques personnalisées doivent être produites, un certain nombre de manières différentes sont disponibles pour implémenter la source de données.
Le type de source de données le plus simple est celui qui fournit une jauge métrique. C'est un cas où une certaine valeur à un moment précis est pertinente et ce qui s'est passé historiquement n'a pas d'importance.
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)
Le décorateur utilisé ici est :
newrelic.agent.data_source_generator(name=None, **properties)
Il est spécifiquement destiné à envelopper une fonction génératrice ou une fonction qui renvoie par ailleurs un itérable lorsqu'elle est appelée.
Le nom lors de l'enregistrement d'une source de données est facultatif. Son rôle principal est de permettre au message de donner un nom plus reconnaissable à la source de données lors du logging des erreurs. Si le nom n'est pas passé à register_data_source()
, alors tout nom associé à la source de données réelle utilisant le décorateur sera utilisé à la place, ou le nom de la fonction si la source de données elle-même n'est pas nommée.
Si un objet d'application n'est pas fourni lors de l'enregistrement d'une source de données, la source de données sera automatiquement associée à toutes les applications pour lesquelles des données sont signalées par l'agent dans ce processus. Si une application est fournie, la source de données sera uniquement associée à cette application spécifique.
Qu'une source de données soit enregistrée explicitement pour une application ou appliquée à toutes les applications, l'agent doit d'abord être enregistré pour cette application. Cela se produit normalement lors de l'utilisation d'une source de données dans un processus d'application Web existant qui est monitoré. Toutefois, si vous utilisez une source de données dans un programme autonome pour signaler uniquement des métriques personnalisées, vous devez toujours vous assurer que l'appel d'API register_application()
est utilisé si nécessaire pour forcer l'enregistrement de l'agent pour une application avant que des données ne soient collectées.
Initialisation d'une source de données
Bien que le décorateur offre la possibilité de nommer une source de données, la raison la plus importante pour laquelle il existe est qu'il masque la complexité d'une séquence d'étapes de configuration pour exécuter une source de données. La séquence de ces étapes est la suivante :
- La source de données est initialisée, avec un dictionnaire contenant toute configuration qui lui est transmise pour la configurer afin qu'elle s'exécute d'une manière particulière.
- Lors de son initialisation, la source de données renvoie un dictionnaire de propriétés décrivant la source de données. Cela inclut une référence à une fonction d'usine pour créer une instance spécifique du fournisseur de source de données.
- Une instance du fournisseur de source de données est ensuite créée pour un consommateur spécifique (application) en appelant la fabrique. La fonction factory reçoit un dictionnaire décrivant l'environnement dans lequel elle s'exécute, y compris le nom du consommateur.
En réécrivant l'exemple ci-dessus pour ne pas dépendre du décorateur, nous aurions :
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)
L’objectif du protocole sous-jacent plus complexe est de fournir suffisamment de points d’ancrage pour initialiser correctement les sources de données et les personnaliser en fonction de cette configuration et des spécificités du consommateur.
instance d'une source de données
Il n'y avait rien d'autre à faire dans l'exemple précédent car les métriques de jauge, qui ne se soucient pas de la dernière fois où elles ont été générées, étaient renvoyées. Lorsqu'une métrique reflète quelque chose qui se produit au fil du temps et doit donc conserver un certain état, nous avons cependant besoin de la possibilité de créer une instance de la source de données.
La fonction d'usine offre donc la possibilité de créer une instance d'une source de données pour chaque application par rapport à laquelle des métriques sont signalées.
Il est possible d'avoir une instance de la source de données par application plutôt qu'une par processus, car les heures de début et de fin du cycle de collecte pour différentes applications peuvent être différentes. S'il n'y en avait qu'un par processus dans ce scénario et que la métrique avait un lien avec la durée du cycle de collecte, alors les métriques résultantes ne seraient pas correctes pour chaque application. Il est donc possible de donner à une instance de source de données la possibilité d'être spécifique à une application.
En utilisant des fonctions imbriquées comme ci-dessus, une source de données qui doit conserver un état pourrait donc être écrite comme suit.
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)
Le décorateur utilisé ici est :
newrelic.agent.data_source_factory(name=None, **properties)
Dans ce cas, le décorateur enveloppe une fonction d'usine. Étant donné que le décorateur renvoie automatiquement les propriétés de la source de données lorsque cela est nécessaire, l'usine prend à la fois les paramètres et la description de l'environnement dans lequel elle est utilisée.
L'utilisation de fonctions imbriquées est un peu magique et nécessite que le code utilise un dictionnaire sur la stack de la fonction externe pour conserver l'état. L'alternative consiste à implémenter la source de données en tant que classe réelle avec le décorateur appliqué à la classe.
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)
cycle de vie d'une source de données
Bien qu'une source de données puisse produire des métriques à tout moment, l'agent lui-même ne signale pas toujours les métriques d'une application. Plus précisément, il ne commencera à collecter des métriques et à les signaler qu'une fois que l'agent aura réussi à s'enregistrer auprès du collecteur de données pour une application spécifique.
Cette distinction est importante pour les sources de données qui génèrent des métriques basées sur une période donnée. Il serait nécessaire de disposer uniquement de mesures produites par une source de données pour couvrir la période remontant au moment où l'enregistrement a eu lieu, ou remontant à la dernière fois où les mesures ont été signalées par l'agent. Si cela n'est pas fait, les métriques signalées ne s'aligneront pas et il ne sera donc pas possible de garantir qu'elles correspondent correctement aux métriques issues du suivi des transactions sur le Web ou des tâches en arrière-plan.
Pour cette raison, l'usine d'une source de données ne sera appelée pour créer une instance de la source de données que lorsque l'enregistrement de l'application sera terminé et que la collecte des métriques aura commencé. Cela garantit que tout horodatage de référence sera correct.
Si l'agent exécuté pour une application particulière est interrompu, en raison d'un redémarrage forcé côté serveur résultant de modifications de configuration côté serveur, ou en raison d'échecs successifs de communication des données au collecteur de données, la source de données sera supprimée. Une nouvelle instance de la source de données sera alors créée lorsque l'agent aura pu se réinscrire à nouveau pour l'application.
Le nettoyage correct d'une source de données dans ce cas dépendra de la destruction prompt de l'objet source de données lorsqu'il est supprimé. En raison des cycles de comptage des références d'objet, on ne peut pas s'y fier. Il est également souhaitable d'éviter qu'une source de données ait besoin d'ajouter une méthode __del__()
afin de déclencher des actions de nettoyage en raison des problèmes qu'une méthode __del__()
introduit dans la manière d'empêcher réellement la destruction prompt de l'objet.
Pour cette raison, si une source de données a besoin de plus de contrôle sur la configuration et l'arrêt, y compris peut-être la possibilité de rester persistante en mémoire et de ne pas être supprimée, tout en suspendant les calculs pour les métriques, elle peut alors fournir les méthodes start()
et stop()
lors de son implémentation en tant qu'instance de classe.
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)
Avec les méthodes start()
et stop()
définies, l'instance de la source de données ne sera pas détruite à la fin de l'exécution de l'agent mais conservée. À ce stade, l'agent s'attend alors à ce que la source de données gère elle-même la suspension de toute agrégation de métriques, en supprimant toutes les métriques accumulées et en garantissant que lorsque l'agent réenregistre l'application auprès du collecteur de données et appelle à nouveau start()
, ce n'est qu'à ce moment-là que le suivi des métriques reprendra.
Configuration d'une source de données
Les sources de données ne sont pas toujours liées à une source d’information spécifique. Il peut être nécessaire d'enregistrer une source de données par rapport à différentes sources d'informations sous-jacentes à partir desquelles les métriques sont générées. Dans ce cas, des paramètres distincts peuvent être transmis lors de l'enregistrement d'une source de données à l'aide de la fonction register_data_source()
. Lors de l'utilisation d'une fabrique de données, ces paramètres seront alors disponibles lors de l'initialisation de la source de données.
@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'))
Si la fourniture des paramètres est facultative, la source de données ne doit tenter d'accéder aux paramètres que si l'option settings
n'est pas None
. Même si un dictionnaire est fourni, il doit également gérer les paramètres manquants dans le dictionnaire.
Installation à partir du fichier de configuration
Bien que les exemples ici montrent l'utilisation de l'appel d'API register_data_source()
, cela ne serait pas la manière normale par laquelle les sources de données seraient enregistrées. Ce n’est pas la méthode préférée car cela nécessiterait des modifications de l’application pour importer le module de la source de données et l’enregistrer.
Au lieu de cela, la principale façon de définir et d’intégrer des sources de données dans une application Web de monitoring existante serait de les répertorier dans le fichier configuration de l’agent. Cela implique l'ajout d'une section supplémentaire dans le fichier de configuration de l'agent pour chaque source de données avec le préfixe data-source:
:
[data-source:process-info]enabled = truefunction = samplers.process_info:process_info_data_source
Si vous enregistrez une source de données à partir du fichier de configuration de l'agent, aucun enregistrement distinct pour la même source de données ne doit être effectué à l'aide de la fonction register_data_source()
apparaissant dans votre code d'application ou dans le module définissant la source de données. Si tel est le cas, deux instances de la source de données finiront par être enregistrées.
Si vous devez fournir des paramètres spécifiques pour une source de données, vous pouvez le faire en créant une section distincte dans le fichier de configuration de l'agent et en faisant référence au nom de la section dans la valeur settings
de la configuration de la source de données.
[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
Étant donné que les paramètres de source de données fournis via le fichier de configuration seront toujours transmis sous forme de valeurs de chaîne, il est recommandé que, même lors de l'utilisation de register_data_source()
avec le code d'application pour enregistrer une source de données et fournir des paramètres explicitement, des chaînes soient utilisées pour définir des valeurs. La source de données doit ensuite gérer la conversion vers un type différent, tel qu'une valeur numérique ou une liste de valeurs.