非常干货:Python 探针实现原理
本文呢,将简单讲述一下 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 sysclass 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] = sysreturn syssys.meta_path.insert(0, MetaPathFinder())if __name__ == '__main__':import httpprint(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 timedef 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 resultreturn wrapperdef sleep(n):time.sleep(n)return nif __name__ == '__main__':func = func_wrapper(sleep)print(func(3))
执行结果:
$ python func_wrapper.py start func spent 3.004966974258423s 3
下面我们来实现一个计算指定模块的指定函数的执行时间的功能(代码可以从 github 上下载 part3) 。
假设我们的模块文件是 hello.py:
import timedef sleep(n):time.sleep(n)return n
我们的 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`` 中保存的是已经导入过的 moduleif fullname in sys.modules:return sys.modules[fullname]# 先从 sys.meta_path 中删除自定义的 finder# 防止下面执行 import_module 的时候再次触发此 finder# 从而出现递归调用的问题finder = sys.meta_path.pop(0)# 导入 modulemodule = importlib.import_module(fullname)module_hook(fullname, module)sys.meta_path.insert(0, finder)return modulesys.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 resultreturn wrapper
测试代码:
>>> import hook >>> import hello find_module hello load_module hello >>> >>> hello.sleep(3) start func spent 3.0029919147491455s 3 >>>
其实上面的代码已经实现了探针的基本功能。不过有一个问题就是上面的代码需要显示的 执行 import hook 操作才会注册上我们定义的 hook。
那么有没有办法在启动 python 解释器的时候自动执行 import hook 的操作呢?答案就是可以通过定义 sitecustomize.py 的方式来实现这个功能。
sitecustomize.py
简单的说就是,python 解释器初始化的时候会自动 import PYTHONPATH 下存在的 sitecustomize 和 usercustomize 模块:
实验项目的目录结构如下(代码可以从 github 上下载 part4) :
$ tree . ├── sitecustomize.py └── usercustomize.py
sitecustomize.py:
$ cat sitecustomize.py print('this is sitecustomize')
usercustomize.py:
$ cat usercustomize.py print('this is 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. >>>
可以看到确实自动导入了。所以我们可以把之前的探测程序改为支持自动执行 import hook (代码可以从 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 吧。
agent
还是在上一个程序的基础上修改。先调整一个目录结构,把 hook 操作放到一个单独的目录下, 方便设置 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 的内容如下:
import os import syscurrent_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()
test.py 的内容为:
$ cat test.py import sys import helloprint(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 探针程序。当然,跟实际使用的探针程序相比肯定是有 很大的差距的,这篇文章主要是讲解一下探针背后的实现原理。
完整代码加群:739021630
非常干货:Python 探针实现原理相关推荐
- python find函数实现原理_非常干货:Python 探针实现原理
△点击上方"Python猫"关注 ,回复" 1 "领取电子书 剧照 | <棋魂> 原文:https://segmentfault.com/a/119 ...
- python QTreeWidgetItem下面有几个子tree_非常干货:Python 探针实现原理
↑↑↑关注后"星标"简说Python 人人都可以简单入门Python.爬虫.数据分析 简说Python推荐 来源:https://segmentfault.com/a/119000 ...
- python 底层原理_Python 探针实现原理
本文将简单讲述一下 Python 探针的实现原理. 同时为了验证这个原理,我们也会一起来实现一个简单的统计指定函数执行时间的探针程序. 探针的实现主要涉及以下几个知识点: sys.meta_path ...
- python程序的原理_Python程序的执行原理(转)
1. 过程概述 Python先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后虚拟机一条一条执行字节码指令,从而完成程序的执行. 2. 字节码 字节码在Python虚拟机程序里对应的是PyCo ...
- python装饰器原理-Python装饰器原理与用法分析
这篇文章主要介绍了Python装饰器原理与用法,结合实例形式分析了Python装饰器的概念.原理.使用方法及相关操作注意事项,需要的朋友可以参考下 本文实例讲述了Python装饰器原理与用法.分享给大 ...
- python装饰器原理-简单了解python装饰器原理及使用方法
这篇文章主要介绍了简单了解python装饰器原理及使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 如果你接触 Python 有一段时间了的话 ...
- python装饰器原理-python装饰器原理与用法深入详解
本文实例讲述了python装饰器原理与用法.分享给大家供大家参考,具体如下: 你会Python嘛? 我会! 那你给我讲下Python装饰器吧! Python装饰器啊?我没用过哎 以上是我一个哥们面试时 ...
- python装饰器实例-Python装饰器原理与简单用法实例分析
本文实例讲述了Python装饰器原理与简单用法.分享给大家供大家参考,具体如下: 今天整理装饰器,内嵌的装饰器.让装饰器带参数等多种形式,非常复杂,让人头疼不已.但是突然间发现了装饰器的奥秘,原来如此 ...
- Python 装饰器原理和基本实现
Python 装饰器原理和基本实现 http://www.cnblogs.com/wupeiqi/articles/4980620.html http://www.cnblogs.com/vamei ...
最新文章
- c#中邮件收发处理(POP3,IMAP,SMTP)的实现方法
- 【读书笔记】iOS-属性列表
- 配置_DruidDataSource参考配置
- 做一个.net 程序员要掌握的知识提纲
- 小波滤波器与其他滤波器的区别_滤波器国产 VS 国外
- 开源作者在行动:疫情防控相关开源项目推荐
- 数组随机排序(随手记)
- oracle安装检测空间china,oracle安装 - Ginn的个人空间 - OSCHINA - 中文开源技术交流社区...
- 查看Linux磁盘文件占用大小
- java8堆内存模型_「GC系列」JVM堆内存分代模型及常见的垃圾回收器
- cdecl.org 翻译C声明的网站
- Python身份证号码识别
- 中铁置业引入USB Server助力RPA机器人
- 企业微信双开及三开的方法
- 2019新版c智播客h马程序员H5全栈工程师培训项目实战
- linux如何破解密码
- 三星手机服务器无影响,终于找到手机网速慢的原因了!原来有这么多讲究
- javascript 去掉html标签,js怎么去掉html标签
- Mac系统随笔 | (2) Macbook WiFi共享
- EXCEL应用:数据可视化终极教程
热门文章
- CSI数据dat文件转换mat文件问题(matlab、python数据处理)
- QGis、Qt对话框上的OK、Open、Cancel、Help等英文翻译
- TypeError: func() missing 1 required positional argument: 'XXXXX' 报错原因
- Axure基础:元件库与基础原件
- 机器学习理论_吃瓜系列1:基本概念
- 【娱乐】exe逆向工程(破译《游戏开发》老师的成绩程序)
- oracle metalink获取,Oracle的MetaLink使用
- DOS命令---定时关机或重启
- 迅雷,qq旋风等下载链接加密算法解析
- 读龙应台《目送》有感