Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Erläuterung des Implementierungsprinzips von Python-Sonden

Detaillierte Erläuterung des Implementierungsprinzips von Python-Sonden

高洛峰
高洛峰Original
2017-03-04 16:07:362138Durchsuche

In diesem Artikel wird kurz das Implementierungsprinzip von Python-Sonden beschrieben. Um dieses Prinzip zu überprüfen, werden wir gleichzeitig auch ein einfaches Sondenprogramm implementieren, das die Ausführungszeit einer bestimmten Funktion zählt.

Die Implementierung des Probe umfasst hauptsächlich die folgenden Wissenspunkte:

sys.meta_path
sitecustomize.py
sys.meta_path

sys.meta_path Das ist Einfach Mit anderen Worten, die Funktion des Import-Hooks kann realisiert werden.
Wenn importbezogene Vorgänge ausgeführt werden, werden die in der Liste sys.meta_path definierten Objekte ausgelöst.
Ausführlichere Informationen zu sys.meta_path finden Sie im sys.meta_path-bezogenen Inhalt in der Python-Dokumentation und
PEP 0302.

Das Objekt in sys.meta_path muss eine find_module-Methode implementieren
Diese find_module-Methode gibt None oder ein Objekt zurück, das die load_module-Methode implementiert
(Der Code kann von Github Teil1 heruntergeladen werden):

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)

Die Methode „load_module“ gibt ein Modulobjekt zurück, das das Modulobjekt des Imports ist.
Zum Beispiel habe ich http durch das sys-Modul ersetzt, wie ich es oben getan habe.

$ python meta_path1.py
find_module http
load_module http

sys.version_info(major=3, minor=5, micro=1, releaselevel='final', serial =0)
Über sys.meta_path können wir die Funktion des Import-Hooks realisieren:
Beim Importieren eines vorgegebenen Moduls wird das Objekt in diesem Modul durch eine Zibetkatze ersetzt,
um die Funktion zu erhalten oder Methode, Ausführungszeit und andere Erkennungsinformationen.

Oben wurde die Zibetkatze für den Prinzen erwähnt. Wie führt man also die Operation der Zibetkatze für den Prinzen an einem Objekt durch?
Für Funktionsobjekte können wir Dekoratoren verwenden, um Funktionsobjekte zu ersetzen (der Code kann von Github Teil2 heruntergeladen werden):

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))

Ergebnis ausführen:

$ python func_wrapper.py
start func
spent 3.004966974258423s
3

Als Nächstes implementieren wir eine Funktion zur Berechnung der Ausführungszeit der angegebenen Funktion des angegebenen Moduls (der Code kann von Github Teil 3 heruntergeladen werden). .

Angenommen, unsere Moduldatei ist hello.py:

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

Unser Import-Hook ist 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

Testcode:

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

Tatsächlich hat der obige Code die Sonde bereits implementiert Grundfunktionen. Es besteht jedoch das Problem, dass der obige Code
anzeigen muss, um den Import-Hook-Vorgang auszuführen und den von uns definierten Hook zu registrieren.

Gibt es also eine Möglichkeit, den Import-Hook-Vorgang automatisch auszuführen, wenn der Python-Interpreter gestartet wird?
Die Antwort ist, dass Sie diese Funktion erreichen können, indem Sie sitecustomize.py definieren.

sitecustomize.py
Um es einfach auszudrücken: Wenn der Python-Interpreter initialisiert wird, importiert er automatisch die Sitecustomize- und Usercustomize-Module, die unter PYTHONPATH vorhanden sind:

Die Verzeichnisstruktur des Experiments Das Projekt lautet wie folgt (der Code kann Teil 4 von Github herunterladen)

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

$ cat sitecustomize.py
print('das ist sitecustomize')
usercustomize.py:

$ cat usercustomize.py
print('das ist usercustomize' )
Fügen Sie das aktuelle Verzeichnis zu PYTHONPATH hinzu und sehen Sie den Effekt:

$ 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.
>>>

Sie können sehen, dass es tatsächlich automatisch importiert wird. Daher können wir das vorherige Erkennungsprogramm ändern, um die automatische Ausführung des Import-Hooks zu unterstützen (der Code kann von Github Teil 5 heruntergeladen werden).

Verzeichnisstruktur:

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

$ cat sitecustomize.py
import hook

Ergebnis:

$ 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

Allerdings Es gibt tatsächlich ein weiteres Problem mit dem oben genannten Erkennungsprogramm: PYTHONPATH muss manuell geändert werden. Freunde, die Sondenprogramme verwendet haben, werden sich daran erinnern, dass Sie zur Verwendung von Sonden wie Newrelic nur einen Befehl ausführen müssen: newrelic-admin run-program python hello.py Tatsächlich befindet sich der Vorgang zum Ändern von PYTHONPATH im Programm newrelic-admin. abgeschlossen in.

Jetzt werden wir auch ein ähnliches Befehlszeilenprogramm implementieren, nennen wir es agent.py.

Agent
ist immer noch basierend auf dem vorherigen Programm modifiziert. Passen Sie zunächst eine Verzeichnisstruktur an und legen Sie die Hook-Operation in einem separaten Verzeichnis ab, damit nach dem Festlegen von PYTHONPATH keine weiteren Störungen auftreten (der Code kann von Github Teil 6 heruntergeladen werden).

$ 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

Der Inhalt von bootstrap/sitecustomize.py wird geändert in:

$ cat bootstrap/sitecustomize.py
import _hookagent.py lautet wie folgt:

<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>

Der Inhalt von test.py lautet:

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

Verwendung:

$ 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

Zu diesem Zeitpunkt haben wir ein einfaches Python-Probeprogramm implementiert. Natürlich gibt es im Vergleich zum tatsächlich verwendeten Sondenprogramm definitiv eine große Lücke. In diesem Artikel wird hauptsächlich das Implementierungsprinzip der Sonde erläutert.

Wenn Sie an der spezifischen Implementierung kommerzieller Sondenprogramme interessiert sind, können Sie sich den Quellcode kommerzieller Python-Sonden von ausländischen New Relic- oder inländischen OneAPM-, TingYun- und anderen APM-Herstellern ansehen finde einige sehr interessante Dinge.

Ausführlichere Erläuterungen zu den Implementierungsprinzipien von Python-Sonden finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn