<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          非常干貨:Python 探針實(shí)現(xiàn)原理

          共 6376字,需瀏覽 13分鐘

           ·

          2020-11-25 19:53

          ?△點(diǎn)擊上方Python貓”關(guān)注 ,回復(fù)“1”領(lǐng)取電子書

          劇照 |?《棋魂》
          原文:https://segmentfault.com/a/1190000004889212

          大家好,我是貓哥。

          關(guān)于 Python 中探針的運(yùn)用,我之前寫過一篇《由淺入深:Python 中如何實(shí)現(xiàn)自動(dòng)導(dǎo)入缺失的庫?》,最近看到一篇文章專門寫這個(gè)內(nèi)容,特分享一下~~

          本文呢,將簡(jiǎn)單講述一下 Python 探針的實(shí)現(xiàn)原理。同時(shí)為了驗(yàn)證這個(gè)原理,我們也會(huì)一起來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的統(tǒng)計(jì)指定函數(shù)執(zhí)行時(shí)間的探針程序。

          探針的實(shí)現(xiàn)主要涉及以下幾個(gè)知識(shí)點(diǎn):

          • sys.meta_path
          • sitecustomize.py

          sys.meta_path

          sys.meta_path 這個(gè)簡(jiǎn)單的來說就是可以實(shí)現(xiàn) import hook 的功能, 當(dāng)執(zhí)行 import 相關(guān)的操作時(shí),會(huì)觸發(fā) sys.meta_path 列表中定義的對(duì)象。關(guān)于 sys.meta_path 更詳細(xì)的資料請(qǐng)查閱 python 文檔中 sys.meta_path 相關(guān)內(nèi)容以及 PEP 0302 。

          sys.meta_path 中的對(duì)象需要實(shí)現(xiàn)一個(gè) find_module 方法, 這個(gè) find_module 方法返回 None 或一個(gè)實(shí)現(xiàn)了 load_module 方法的對(duì)象 (代碼可以從 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 方法返回一個(gè) module 對(duì)象,這個(gè)對(duì)象就是 import 的 module 對(duì)象了。比如我上面那樣就把 http 替換為 sys 這個(gè) 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 我們就可以實(shí)現(xiàn) import hook 的功能:當(dāng) import 預(yù)定的 module 時(shí),對(duì)這個(gè) module 里的對(duì)象來個(gè)貍貓換太子, 從而實(shí)現(xiàn)獲取函數(shù)或方法的執(zhí)行時(shí)間等探測(cè)信息。

          上面說到了貍貓換太子,那么怎么對(duì)一個(gè)對(duì)象進(jìn)行貍貓換太子的操作呢?對(duì)于函數(shù)對(duì)象,我們可以使用裝飾器的方式來替換函數(shù)對(duì)象(代碼可以從 github 上下載 part2) :

          import?functools
          import?time

          def?func_wrapper(func):
          [email protected](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))

          執(zhí)行結(jié)果:

          $?python?func_wrapper.py
          start?func
          spent?3.004966974258423s
          3

          下面我們來實(shí)現(xiàn)一個(gè)計(jì)算指定模塊的指定函數(shù)的執(zhí)行時(shí)間的功能(代碼可以從 github 上下載 part3) 。

          假設(shè)我們的模塊文件是 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``?中保存的是已經(jīng)導(dǎo)入過的?module
          ????????if?fullname?in?sys.modules:
          ????????????return?sys.modules[fullname]

          ????????#?先從?sys.meta_path?中刪除自定義的?finder
          ????????#?防止下面執(zhí)行?import_module?的時(shí)候再次觸發(fā)此?finder
          ????????#?從而出現(xiàn)遞歸調(diào)用的問題
          ????????finder?=?sys.meta_path.pop(0)
          ????????#?導(dǎo)入?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):
          [email protected](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

          測(cè)試代碼:

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

          其實(shí)上面的代碼已經(jīng)實(shí)現(xiàn)了探針的基本功能。不過有一個(gè)問題就是上面的代碼需要顯示的 執(zhí)行 import hook 操作才會(huì)注冊(cè)上我們定義的 hook。

          那么有沒有辦法在啟動(dòng) python 解釋器的時(shí)候自動(dòng)執(zhí)行 import hook 的操作呢?答案就是可以通過定義 sitecustomize.py 的方式來實(shí)現(xiàn)這個(gè)功能。

          sitecustomize.py

          簡(jiǎn)單的說就是,python 解釋器初始化的時(shí)候會(huì)自動(dòng) import PYTHONPATH 下存在的 sitecustomizeusercustomize 模塊:

          實(shí)驗(yàn)項(xiàng)目的目錄結(jié)構(gòu)如下(代碼可以從 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')

          把當(dāng)前目錄加到 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.
          >>>

          可以看到確實(shí)自動(dòng)導(dǎo)入了。所以我們可以把之前的探測(cè)程序改為支持自動(dòng)執(zhí)行 import hook (代碼可以從 github 上下載 part5) 。

          目錄結(jié)構(gòu):

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

          sitecustomize.py:

          $?cat?sitecustomize.py
          import?hook

          結(jié)果:

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

          不過上面的探測(cè)程序其實(shí)還有一個(gè)問題,那就是需要手動(dòng)修改 PYTHONPATH 。用過探針程序的朋友應(yīng)該會(huì)記得, 使用 newrelic 之類的探針只需要執(zhí)行一條命令就 可以了:newrelic-admin run-program python hello.py 實(shí)際上修改 PYTHONPATH 的操作是在newrelic-admin 這個(gè)程序里完成的。

          下面我們也要來實(shí)現(xiàn)一個(gè)類似的命令行程序,就叫 agent.py 吧。

          agent

          還是在上一個(gè)程序的基礎(chǔ)上修改。先調(diào)整一個(gè)目錄結(jié)構(gòu),把 hook 操作放到一個(gè)單獨(dú)的目錄下, 方便設(shè)置 PYTHONPATH 后不會(huì)有其他的干擾(代碼可以從 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 的內(nèi)容修改為:

          $?cat?bootstrap/sitecustomize.py
          import?_hook

          agent.py 的內(nèi)容如下:

          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
          ????#?執(zhí)行后面的?python?程序命令
          ????#?sys.executable?是?python?解釋器程序的絕對(duì)路徑?``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 的內(nèi)容為:

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

          至此,我們就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 python 探針程序。當(dāng)然,跟實(shí)際使用的探針程序相比肯定是有 很大的差距的,這篇文章主要是講解一下探針背后的實(shí)現(xiàn)原理。

          文中的代碼:https://github.com/mozillazg/apm-python-agent-principle

          Python貓技術(shù)交流群開放啦!群里既有國內(nèi)一二線大廠在職員工,也有國內(nèi)外高校在讀學(xué)生,既有十多年碼齡的編程老鳥,也有中小學(xué)剛剛?cè)腴T的新人,學(xué)習(xí)氛圍良好!想入群的同學(xué),請(qǐng)?jiān)诠?hào)內(nèi)回復(fù)『交流群』,獲取貓哥的微信(謝絕廣告黨,非誠勿擾!)~

          近期熱門文章推薦:

          全網(wǎng)最硬核講解計(jì)算機(jī)的啟動(dòng)過程
          11 個(gè)最佳的 Python 編譯器和解釋器
          Python 3.8 帶來了哪些新鮮功能?
          Python 3.8 已發(fā)布,那如何編譯和調(diào)試最新的內(nèi)核源碼呢?

          感謝創(chuàng)作者的好文
          瀏覽 41
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.日本 爽久久.cou | 一本久道激情淫乱视频 | 亚洲日韩欧美第一页 | 精品自在线| 黑人操逼电影 |