이 글에서는 Python 프로브의 구현 원리를 간략하게 설명합니다. 동시에 이 원리를 검증하기 위해 지정된 함수의 실행 시간을 계산하는 간단한 프로브 프로그램도 구현하겠습니다.
프로브 구현에는 주로 다음 지식 포인트가 포함됩니다.
sys.meta_path
sitecustomize.py
sys.meta_path
sys.meta_path는 다음과 같습니다. simple 즉, import Hook 기능을 구현할 수 있습니다.
import 관련 작업을 수행하면 sys.meta_path 목록에 정의된 개체가 트리거됩니다.
sys.meta_path에 대한 자세한 내용은 Python 설명서의 sys.meta_path 관련 내용과
PEP 0302를 참조하세요.
sys.meta_path의 개체는 find_module 메서드를 구현해야 합니다.
이 find_module 메서드는 None 또는 load_module 메서드를 구현하는 개체를 반환합니다.
(코드는 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)
load_module 메소드는 가져오기의 모듈 객체인 모듈 객체를 반환합니다.
예를 들어 위에서 했던 것처럼 http를 sys 모듈로 대체했습니다.
$ python Meta_path1.py
find_module http
load_module http
sys.version_info(major=3, minor=5, micro=1, releaselevel='final', serial =0)
sys.meta_path를 통해 가져오기 후크 기능을 실현할 수 있습니다.
미리 결정된 모듈을 가져올 때 이 모듈의 개체는 사향고양이로 대체되어
기능 획득을 실현합니다. 또는 방법 실행 시간 및 기타 탐지 정보.
위에서 왕자를 위한 사향고양이를 언급했는데, 왕자를 위한 사향고양이 동작을 어떻게 물체에 수행할 수 있을까요?
함수 개체의 경우 데코레이터를 사용하여 함수 개체를 대체할 수 있습니다(코드는 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))
실행 결과:
$ python func_wrapper.py start func spent 3.004966974258423s 3
이제 지정된 모듈의 지정된 함수의 실행 시간을 계산하는 함수를 구현해 보겠습니다. (코드는 github part3에서 다운로드할 수 있습니다.)
모듈 파일이 hello.py라고 가정합니다.
import time def sleep(n): time.sleep(n) return n
가져오기 후크는 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
테스트 코드:
>>> import hook >>> import hello find_module hello load_module hello >>> >>> hello.sleep(3) start func spent 3.0029919147491455s 3 >>>
사실 위 코드는 프로브의 기본 기능을 구현한 것입니다. 그러나 우리가 정의한 Hook을 등록하기 위해서는 import Hook 연산을 실행하기 위해 위 코드에서
를 표시해야 한다는 문제가 있습니다.
그럼 파이썬 인터프리터를 시작할 때 import Hook 작업을 자동으로 실행하는 방법이 있나요?
답은 sitecustomize.py를 정의하여 이 기능을 구현할 수 있다는 것입니다.
sitecustomize.py
간단히 말하면 Python 인터프리터가 초기화되면 PYTHONPATH 아래에 있는 sitecustomize 및 usercustomize 모듈을 자동으로 가져옵니다.
실험의 디렉터리 구조 프로젝트는 다음과 같습니다(코드는 github에서 part4를 다운로드할 수 있음)
$ tree
.
├── sitecustomize.py
└── usercustomize.py
sitecustomize.py:
$ cat sitecustomize.py
print('sitecustomize입니다')
usercustomize.py:
$ cat usercustomize.py
print('usercustomize입니다' )
현재 디렉터리를 PYTHONPATH에 추가하고 효과를 확인하세요.
$ 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. >>>
실제로 자동으로 가져오는 것을 확인할 수 있습니다. 따라서 가져오기 후크의 자동 실행을 지원하도록 이전 감지 프로그램을 변경할 수 있습니다(코드는 github part5에서 다운로드할 수 있음).
디렉터리 구조:
$ tree
.
├── hello.py
├── Hook.py
├── sitecustomize.py
sitecustomize.py:
$ cat sitecustomize.py import hook
결과:
$ 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
그러나 위의 탐지는 프로그램 사실 또 다른 문제가 있습니다. 즉, PYTHONPATH를 수동으로 수정해야 한다는 것입니다. 프로브 프로그램을 사용해 본 친구들은 newrelic과 같은 프로브를 사용하려면 다음 명령 하나만 실행하면 된다는 것을 기억할 것입니다: newrelic-admin run-program python hello.py 실제로 PYTHONPATH를 수정하는 작업은 newrelic-admin 프로그램에 있습니다. 에 완료되었습니다.
이제 유사한 명령줄 프로그램도 구현하겠습니다. 이름은 Agent.py입니다.
에이전트
는 여전히 이전 프로그램을 기반으로 수정되었습니다. 먼저 디렉토리 구조를 조정하고 PYTHONPATH를 설정한 후 다른 간섭이 없도록 별도의 디렉토리에 후크 작업을 배치합니다(코드는 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
bootstrap/sitecustomize.py의 콘텐츠는 다음과 같이 수정됩니다.
$ cat bootstrap/sitecustomize.py
import _hook
Agent.py 내용은 다음과 같습니다.
<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>
test.py 내용은
$ cat test.py import sys import hello print(sys.argv) print(hello.sleep(3))
사용법:
$ 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
이 시점에서 간단한 Python 프로브 프로그램을 구현했습니다. 물론 실제 사용되는 프로브 프로그램과 비교하면 확실히 큰 차이가 있습니다. 이 글에서는 주로 프로브의 구현 원리를 설명합니다.
상용 프로브 프로그램의 구체적인 구현에 관심이 있으시면 해외 New Relic이나 국내 OneAPM, TingYun 및 기타 APM 제조업체의 상용 Python 프로브 소스 코드를 살펴보실 수 있을 것으로 믿습니다. 아주 흥미로운 것들을 찾아보세요.
Python 프로브의 구현 원리와 관련 기사에 대한 자세한 설명은 PHP 중국어 웹사이트를 참고하세요!