Newrelic 是APM(Application Performance Management)(應用性能管理/監控)解決方案提供商。項目中,一般用它來追蹤應用的性能。最近看了一下 newrelic-python-agent 源碼,這是查看源碼過程當中的一些記錄。html
newrelic 目錄結構以下:python
newrelic
├── admin # 經常使用命令
├── api # 探針
├── bootstrap
├── common
├── core
├── extras
│ └── framework_django
│ └── templatetags
├── hooks # 數據庫 web 各個庫的一些探針
│ ├── framework_tornado
│ ├── framework_tornado_r3
│ └── framework_tornado_r4
├── network
├── packages
│ ├── requests
│ │ └── packages
│ │ ├── chardet
│ │ └── urllib3
│ │ ├── packages
│ │ │ └── ssl_match_hostname
│ │ └── util
│ └── wrapt
└── samplers
複製代碼
使用 newrelic-admin help
能夠列出全部命令:web
$ newrelic-admin help
Usage: newrelic-admin command [options]
Type 'newrelic-admin help <command>'for help on a specific command.
Available commands are:
generate-config
license-info
license-key
local-config
network-config
record-deploy
run-program
run-python
server-config
validate-config
複製代碼
經過 setup.py 代碼能夠知道:數據庫
if with_setuptools:
kwargs['entry_points'] = {
'console_scripts': ['newrelic-admin = newrelic.admin:main'],
}
複製代碼
newrelic-admin
命令調用的是 newrelic.admin:main
,這是代碼的入口。首先看一下 newrelic/admin/
目錄。django
admin 目錄是 newrelic-admin help 列出的命令腳本所在目錄。flask
包含文件以下:bootstrap
$ tree
admin
├── __init__.py
├── __main__.py
├── debug_console.py
├── generate_config.py
├── license_info.py
├── license_key.py
├── local_config.py
├── network_config.py
├── record_deploy.py
├── run_program.py
├── run_python.py
├── server_config.py
└── validate_config.py
複製代碼
__init__.py
的 main 函數 是命令執行的入口。小程序
__init__.py
文件中代碼設計模式
load_internal_plugins()
load_external_plugins()
複製代碼
用來加載 _builtin_plugins
中定義的命令。api
首先看下 run_program 命令,這個命令使用方式以下:
newrelic-admin run-program your command
複製代碼
newrelic/admin/run_program.py
中 run_program
函數有裝飾器 command,用來定義將命令以及相關說明添加到字典 _commands
。
在 run_program
中代碼:
root_directory = os.path.dirname(root_directory)
boot_directory = os.path.join(root_directory, 'bootstrap')
if 'PYTHONPATH' in os.environ:
path = os.environ['PYTHONPATH'].split(os.path.pathsep)
if not boot_directory in path:
python_path = "%s%s%s" % (boot_directory, os.path.pathsep, os.environ['PYTHONPATH'])
os.environ['PYTHONPATH'] = python_path
複製代碼
能夠發現newrelic/bootstrap/sitecustomize.py
文件被加入到了 PYTHONPATH。
python 解釋器初始化的時候會自動 import
PYTHONPATH
下存在的sitecustomize
和usercustomize
模塊。
以後的功能比較簡單,就是調用 os 模塊執行命令。
如今看下newrelic/bootstrap/sitecustomize.py
代碼。
在 這個文件的最後一行:
newrelic.config.initialize(config_file, environment)
複製代碼
這裏用來初始化newrelic,具體代碼在 newrelic/config.py
文件。
如下是initialize函數:
def initialize(config_file=None, environment=None, ignore_errors=None, log_file=None, log_level=None):
if config_file is None:
config_file = os.environ.get('NEW_RELIC_CONFIG_FILE', None)
if environment is None:
environment = os.environ.get('NEW_RELIC_ENVIRONMENT', None)
if ignore_errors is None:
ignore_errors = newrelic.core.config._environ_as_bool(
'NEW_RELIC_IGNORE_STARTUP_ERRORS', True)
_load_configuration(config_file, environment, ignore_errors,
log_file, log_level) # 加載配置
if _settings.monitor_mode or _settings.developer_mode:
_settings.enabled = True
_setup_instrumentation() # 設置探針
_setup_data_source() # TODO
_setup_extensions() # TODO
_setup_agent_console() # TODO
else:
_settings.enabled = False
複製代碼
其中第14行 _load_configuration
是用來加載 newrelic 的相關配置。好比:日誌目錄、各類環境變量、祕鑰、newrelic host 地址等等。
`_setup_instrumentation() 中 _process_module_builtin() 用來設置探針。
數據庫、外部請求 等監控模塊都位於 hook 目錄下,經過 _process_module_builtin
函數將進程與監控模塊進行綁定,包括 django 的主要模塊以及經常使用的數據庫等。在覈心模塊執行的時候觸發監控,將數據回傳到 api.time_trace
模塊進行處理。
而對於硬件信息的檢測則由 commo.system_info
進行。
如下爲 flask 應用初始化過程,其它應用相似:
newrelic/admin/__init__.py main()
newrelic/admin/run_program.py
代碼中會把 newrelic/bootstrap/sitecustomize.py
添加到 PYTHONPATH
,python 解釋器初始化的時候會自動 import PYTHONPATH
下存在的 sitecustomize
和 usercustomize
模塊newrelic/bootstrap/sitecustomize.py
調用 newrelic.config.initialize()
,_setup_instrumentation()
函數被調用,_process_module_builtin
會把須要 wrap 的包先添加到_import_hooks。newrelic/config.py
中 sys.meta_path.insert(0, newrelic.api.import_hook.ImportHookFinder())
執行newrelic/api/import_hook.py ImportHookFinder().find_model()
newrelic/api/import_hook.py _ImportHookLoader() or _ImportHookChainedLoader()
newrelic/api/import_hook.py _notify_import_hooks
callable
爲 newrelic/config _module_import_hook _instrument
newrelic/hooks/framework_flask.py instrument_flask_app
newrelic/api/web_transaction.py wrap_wsgi_application
newrelic/common/object_wrapper.py wrap_object
在代碼中,使用到了第三方包 wrapt
,如下是 wrapt 的官方描述(文檔地址)。
wrapt模塊的目的是爲Python提供一個透明的對象代理,它能夠做爲構建函數包裝器和裝飾函數的基礎。wrapt 提供了一個簡單易用的decorator工廠,利用它你能夠簡單地建立decorator,而且在任何狀況下均可以正確地使用它們。
wrapt
簡單示例以下:
import wrapt
# 普通裝飾器
@wrapt.decorator
def pass_through(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)
@pass_through
def function():
pass
# 帶參數的裝飾器
import wrapt
def with_arguments(myarg1, myarg2):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)
return wrapper
@with_arguments(1, 2)
def function():
pass
複製代碼
要實現decorator,須要首先定義一個裝飾器函數。這將在每次調用修飾函數時調用。裝飾器函數須要使用四個位置參數:
具體使用參考文檔吧。 文檔地址
newrelic 源碼仔細看下去,太...複雜了。下一篇再分析一個 flask 請求到結束探針工做的完整過程吧。
若是下一篇一直沒寫,那應該是看不下去了。
最後,感謝女友支持和包容,比❤️
也能夠在公號輸入如下關鍵字獲取歷史文章:公號&小程序
| 設計模式
| 併發&協程