Maison > Article > développement back-end > Explication détaillée du principe d'implémentation des sondes Python
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">'bootstrap'</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">'PYTHONPATH'</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"># '/usr/local/var/pyenv/versions/3.5.1/bin/python3.5'</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">'__main__'</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 ['test.py', 'arg1', 'arg2'] 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 !