我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Python 探针的实现原理

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

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

执行结果:


$ python func_wrapper.py
start func
spent 3.004966974258423s
3

下面我们来实现一个计算指定模块的指定函数的执行时间的功能(代码可以从 github 上下载 part3) 。

假设我们的模块文件是 hello.py:


import time
 
def 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`` 中保存的是已经导入过的 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
>>>

其实上面的代码已经实现了探针的基本功能。不过有一个问题就是上面的代码需要显示的
执行 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 的内容如下:


<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 探针的源代码,相信你会发现一些很有趣的事情。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Python 探针的实现原理

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python 探针的实现原理

探针的实现主要涉及以下几个知识点: sys.meta_path sitecustomize.py sys.meta_path sys.meta_path 这个简单的来说就是可以实现 import hook 的功能, 当执行 import 相
2022-06-04

Python 探针实现原理

本文将简单讲述一下 Python 探针的实现原理。 同时为了验证这个原理,我们也会一起来实现一个简单的统计指定函数执行时间的探针程序。探针的实现主要涉及以下几个知识点:sys.meta_pathsitecustomize.pysys.met
2023-01-31

Python中探针的实现原理是什么

这篇文章将为大家详细讲解有关Python中探针的实现原理是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。sys.meta_pathsys.meta_path 这个简单的来说就是可以实现
2023-06-15

reduce探索lodash.reduce实现原理解析

这篇文章主要介绍了reduce探索lodash.reduce实现原理解析
2023-02-27

深入探究Java @MapperScan实现原理

之前是直接在Mapper类上面添加注解@Mapper,这种方式要求每一个mapper类都需要添加此注解,麻烦。通过使用@MapperScan可以指定要扫描的Mapper类的包的路径,这篇文章深入探究Java @MapperScan的实现原理
2023-01-04

探究实现Aware接口的原理及使用

这篇文章主要为大家介绍了探究实现Aware接口的原理及使用,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-18

探索Golang中除法运算符的实现原理

探索Golang中除法运算符的实现原理在Golang中,除法运算符/用于执行除法计算。但是,要了解除法运算符的实现原理,我们需要深入了解Golang中的底层实现机制。Golang是一门编译型语言,其底层代码由编译器生成。因此,我们无法直接
探索Golang中除法运算符的实现原理
2024-03-13

深入探索:Go WaitGroup的原理和内部实现

WaitGroup是Go语言中的一个并发同步原语,用于等待一组goroutine的完成。它提供了三个主要的方法:Add、Done和Wait。Add方法用于向WaitGroup中添加要等待的goroutine的数量。Done方法用于表示一个g
2023-10-08

python带你探寻WSGI Application原理

这篇文章主要为大家介绍了python学习探寻WSGI Application原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-17

LRUCache的实现原理及利用python实现的方法

简介 LRU(Least Recently Used)最近最少使用,最近有时间和空间最近的歧义,所以我更喜欢叫它近期最少使用算法。它的核心思想是,如果一个数据被访问过,我们有理由相信它在将来被访问的概率就越高。于是当LRU缓存达到设定的最大
2022-06-04

PHP 自动加载的深入探索:从原理到实现

PHP 自动加载深入探索:从原理到实现
PHP 自动加载的深入探索:从原理到实现
2024-03-01

深入探讨Go语言中chan通道的实现原理

Go语言作为一种并发编程语言,具有轻量级线程(goroutine)和通道(channel)等特性,其中通道是一种用于在goroutine之间传递数据的重要机制。在本文中,我们将深入探讨Go语言中chan通道的实现原理,并结合具体的代码示例进
深入探讨Go语言中chan通道的实现原理
2024-03-13

C++ 智能指针:探索内存管理的最佳实践

c++++ 智能指针提供了对堆上分配对象的内存管理,包括独占所有权的 std::unique_ptr、共享所有权的 std::shared_ptr,以及用于跟踪对象存在的 std::weak_ptr。通过使用这些智能指针,可以自动释放内存并
C++ 智能指针:探索内存管理的最佳实践
2024-05-08

深入探究len函数在Python中的实现原理:深入理解其底层机制

深入理解Python中的len函数:掌握其底层实现原理,需要具体代码示例引言:Python是一门简洁、易读、容易上手的编程语言。在Python中,len()函数是一种非常常用的内置函数,用于返回某个容器对象(如字符串、列表、元组等)的元素
深入探究len函数在Python中的实现原理:深入理解其底层机制
2024-01-13

如何进行微信红包实现原理探讨

这篇文章将为大家详细讲解有关如何进行微信红包实现原理探讨,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。一、背景以下内容基于QCon某高可用架构群讨论总结群里某同学问起微信红包架构,腾讯财付通
2023-06-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录