美文网首页
Python 探针实现原理

Python 探针实现原理

作者: hugoren | 来源:发表于2018-05-22 11:35 被阅读0次

    本文将简单讲述一下 Python 探针的实现原理。 同时为了验证这个原理,我们也会一起来实现一个简单的统计指定函数执行时间的探针程序。

    探针的实现主要涉及以下几个知识点:

    • sys.meta_path
    • sitecustomize.py
    • sys.meta_path
      sys.meta_path这个简单的来说就是可以实现 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 方法返回一个 module 对象,这个对象就是 import 的 module 对象了。 比如我上面那样就把 http 替换为 sys 这个 module 了。

    $ python meta_path1.py
    find_module http
    load_module http
    <module 'sys' (built-in)>
    sys.version_info(major=3, minor=5, micro=1, releaselevel='final', serial=0)
    

    通过 sys.meta_path 我们就可以实现 import hook 的功能: 当 import 预定的 module 时,对这个 module 里的对象来个狸猫换太子, 从而实现获取函数或方法的执行时间等探测信息。

    上面说到了狸猫换太子,那么怎么对一个对象进行狸猫换太子的操作呢? 对于函数对象,我们可以使用装饰器的方式来替换函数对象(代码可以从 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))
    
    image.png

    我们的 import hook 是 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
    
    image.png image.png image.png image.png

    实现一个agent

    import os
    import sys
    
    current_dir = os.path.dirname(os.path.realpath(__file__))
    boot_dir = os.path.join(current_dir, 'bootstrap')
    
    
    def main():
        args = sys.argv[1:]
        os.environ['PYTHONPATH'] = boot_dir
        # 执行后面的 python 程序命令
        # sys.executable 是 python 解释器程序的绝对路径 ``which python``
        # >>> sys.executable
        # '/usr/local/var/pyenv/versions/3.5.1/bin/python3.5'
        os.execl(sys.executable, sys.executable, *args)
    
    if __name__ == '__main__':
        main()
    

    转自:
    https://mozillazg.com/2016/04/apm-python-agent-principle.html

    相关文章

      网友评论

          本文标题:Python 探针实现原理

          本文链接:https://www.haomeiwen.com/subject/qpdhjftx.html