Maison  >  Article  >  développement back-end  >  Explication détaillée du principe d'implémentation des sondes Python

Explication détaillée du principe d'implémentation des sondes Python

高洛峰
高洛峰original
2017-03-04 16:07:362131parcourir

Cet article décrira brièvement le principe de mise en œuvre des sondes Python. Parallèlement, afin de vérifier ce principe, nous implémenterons également un programme de sonde simple qui compte le temps d'exécution d'une fonction spécifiée.

La mise en œuvre de la sonde implique principalement les points de connaissances suivants :

sys.meta_path
sitecustomize.py
sys.meta_path

sys.meta_path C'est simple En d'autres termes, la fonction de hook d'importation peut être réalisée
Lorsque les opérations liées à l'importation sont effectuées, les objets définis dans la liste sys.meta_path seront déclenchés.
Pour des informations plus détaillées sur sys.meta_path, veuillez vous référer au contenu associé à sys.meta_path dans la documentation python et
PEP 0302.

L'objet dans sys.meta_path doit implémenter une méthode find_module
Cette méthode find_module renvoie None ou un objet qui implémente la méthode load_module
(Le code peut être téléchargé depuis github part1) :

import sys
 
class MetaPathFinder:
 
  def find_module(self, fullname, path=None):
    print('find_module {}'.format(fullname))
    return MetaPathLoader()
 
class MetaPathLoader:
 
  def load_module(self, fullname):
    print('load_module {}'.format(fullname))
    sys.modules[fullname] = sys
    return sys
 
sys.meta_path.insert(0, MetaPathFinder())
 
if __name__ == '__main__':
  import http
  print(http)
  print(http.version_info)

La méthode load_module renvoie un objet module, qui est l'objet module de l'importation.
Par exemple, j'ai remplacé http par le module sys comme je l'ai fait ci-dessus.

$ python meta_path1.py
find_module http
load_module http

sys.version_info(major=3, minor=5, micro=1, releaselevel='final', série =0)
Grâce à sys.meta_path nous pouvons réaliser la fonction de hook d'importation :
Lors de l'importation d'un module prédéterminé, l'objet de ce module sera remplacé par un chat civette,
afin d'obtenir la fonction ou méthode Temps d'exécution et autres informations de détection.

Ceci ci-dessus a évoqué la civette chat pour le prince, alors comment réaliser l'opération de civette chat pour le prince sur un objet ?
Pour les objets fonction, nous pouvons utiliser des décorateurs pour remplacer les objets fonction (le code peut être téléchargé depuis github part2) :

import functools
import time
 
def func_wrapper(func):
  @functools.wraps(func)
  def wrapper(*args, **kwargs):
    print('start func')
    start = time.time()
    result = func(*args, **kwargs)
    end = time.time()
    print('spent {}s'.format(end - start))
    return result
  return wrapper
 
def sleep(n):
  time.sleep(n)
  return n
 
if __name__ == '__main__':
  func = func_wrapper(sleep)
  print(func(3))

Exécuter le résultat :

$ python func_wrapper.py
start func
spent 3.004966974258423s
3

Ensuite, nous implémenterons une fonction pour calculer le temps d'exécution de la fonction spécifiée du module spécifié (le code peut être téléchargé depuis github part3) .

Supposons que notre fichier de module soit hello.py :

import time
 
def sleep(n):
  time.sleep(n)
  return n

Notre hook d'importation est hook.py :

import functools
import importlib
import sys
import time
 
_hook_modules = {'hello'}
 
class MetaPathFinder:
 
  def find_module(self, fullname, path=None):
    print('find_module {}'.format(fullname))
    if fullname in _hook_modules:
      return MetaPathLoader()
 
class MetaPathLoader:
 
  def load_module(self, fullname):
    print('load_module {}'.format(fullname))
    # ``sys.modules`` 中保存的是已经导入过的 module
    if fullname in sys.modules:
      return sys.modules[fullname]
 
    # 先从 sys.meta_path 中删除自定义的 finder
    # 防止下面执行 import_module 的时候再次触发此 finder
    # 从而出现递归调用的问题
    finder = sys.meta_path.pop(0)
    # 导入 module
    module = importlib.import_module(fullname)
 
    module_hook(fullname, module)
 
    sys.meta_path.insert(0, finder)
    return module
 
sys.meta_path.insert(0, MetaPathFinder())
 
def module_hook(fullname, module):
  if fullname == 'hello':
    module.sleep = func_wrapper(module.sleep)
 
def func_wrapper(func):
  @functools.wraps(func)
  def wrapper(*args, **kwargs):
    print('start func')
    start = time.time()
    result = func(*args, **kwargs)
    end = time.time()
    print('spent {}s'.format(end - start))
    return result
  return wrapper

Code de test :

>>> import hook
>>> import hello
find_module hello
load_module hello
>>>
>>> hello.sleep(3)
start func
spent 3.0029919147491455s
3
>>>

En fait, le code ci-dessus a déjà implémenté la sonde fonctions de base. Cependant, il existe un problème: le code ci-dessus doit afficher
pour exécuter l'opération d'importation du hook afin d'enregistrer le hook que nous avons défini.

Alors, existe-t-il un moyen d'exécuter automatiquement l'opération de hook d'importation lors du démarrage de l'interpréteur Python ?
La réponse est que vous pouvez réaliser cette fonction en définissant sitecustomize.py.

sitecustomize.py
Pour faire simple, lorsque l'interpréteur python sera initialisé, il importera automatiquement les modules sitecustomize et usercustomize qui existent sous PYTHONPATH :

La structure des répertoires de l'expérimental Le projet est le suivant (le code peut télécharger la partie 4 depuis github)

$ tree
.
├── sitecustomize.py
└── usercustomize.py
sitecustomize.py :

$ cat sitecustomize.py
print('c'est sitecustomize')
usercustomize.py:

$ cat usercustomize.py
print('c'est usercustomize' )
mettez le répertoire courant Ajoutez-le à PYTHONPATH et voyez l'effet :

$ export PYTHONPATH=.
$ python
this is sitecustomize    <----
this is usercustomize    <----
Python 3.5.1 (default, Dec 24 2015, 17:20:27)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

Vous pouvez voir qu'il est bien importé automatiquement. Nous pouvons donc modifier le programme de détection précédent pour prendre en charge l'exécution automatique du hook d'importation (le code peut être téléchargé depuis github part5).

Structure du répertoire :

arbre $
.
├── hello.py
├── hook.py
├── sitecustomize.py
sitecustomize.py :

$ cat sitecustomize.py
import hook

Résultat :

$ export PYTHONPATH=.
$ python
find_module usercustomize
Python 3.5.1 (default, Dec 24 2015, 17:20:27)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
find_module readline
find_module atexit
find_module rlcompleter
>>>
>>> import hello
find_module hello
load_module hello
>>>
>>> hello.sleep(3)
start func
spent 3.005002021789551s
3

Cependant Il existe en fait un autre problème avec le programme de détection ci-dessus, à savoir que PYTHONPATH doit être modifié manuellement. Les amis qui ont utilisé des programmes de sonde se souviendront que pour utiliser des sondes telles que newrelic, il suffit d'exécuter une seule commande : newrelic-admin run-program python hello.py En fait, l'opération de modification de PYTHONPATH se trouve dans le programme newrelic-admin. terminé en.

Maintenant, nous allons également implémenter un programme de ligne de commande similaire, appelons-le agent.py.

agent
est toujours modifié en fonction du programme précédent. Ajustez d'abord une structure de répertoires et placez l'opération de hook dans un répertoire séparé afin qu'il n'y ait aucune autre interférence après avoir défini PYTHONPATH (le code peut être téléchargé depuis github part6).

$ mkdir bootstrap
$ mv hook.py bootstrap/_hook.py
$ touch bootstrap/__init__.py
$ touch agent.py
$ tree
.
├── bootstrap
│  ├── __init__.py
│  ├── _hook.py
│  └── sitecustomize.py
├── hello.py
├── test.py
├── agent.py

Le contenu de bootstrap/sitecustomize.py est modifié en :

$ cat bootstrap/sitecustomize.py
import _hookagent.py est le suivant :

<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
 
<span class="n">current_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">realpath</span><span class="p">(</span><span class="n">__file__</span><span class="p">))</span>
<span class="n">boot_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">current_dir</span><span class="p">,</span> <span class="s">&#39;bootstrap&#39;</span><span class="p">)</span>
 
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
  <span class="n">args</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
  <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">&#39;PYTHONPATH&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">boot_dir</span>
  <span class="c"># 执行后面的 python 程序命令</span>
  <span class="c"># sys.executable 是 python 解释器程序的绝对路径 ``which python``</span>
  <span class="c"># >>> sys.executable</span>
  <span class="c"># &#39;/usr/local/var/pyenv/versions/3.5.1/bin/python3.5&#39;</span>
  <span class="n">os</span><span class="o">.</span><span class="n">execl</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">executable</span><span class="p">,</span> <span class="n">sys</span><span class="o">.</span><span class="n">executable</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
 
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&#39;__main__&#39;</span><span class="p">:</span>
  <span class="n">main</span><span class="p">()</span>

Le contenu de test.py est :

$ cat test.py
import sys
import hello
 
print(sys.argv)
print(hello.sleep(3))

Utilisation :

$ python agent.py test.py arg1 arg2
find_module usercustomize
find_module hello
load_module hello
[&#39;test.py&#39;, &#39;arg1&#39;, &#39;arg2&#39;]
start func
spent 3.005035161972046s
3

À ce stade, nous avons implémenté un simple programme de sonde Python. Bien sûr, il y a certainement un grand écart par rapport au programme de sonde actuellement utilisé. Cet article explique principalement le principe de mise en œuvre derrière la sonde.

Si vous êtes intéressé par la mise en œuvre spécifique de programmes de sondes commerciales, vous pouvez jeter un œil au code source des sondes Python commerciales de New Relic étranger ou de OneAPM national, TingYun et d'autres fabricants d'APM, je pense que vous le ferez. trouver des choses très intéressantes.

Pour des explications plus détaillées sur les principes d'implémentation des sondes Python et des articles associés, veuillez faire attention au site Web PHP chinois !

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn