Newrelic 是APM(Application Performance Management)(應用效能管理/監控)解決方案提供商。專案中,通常用它來追蹤應用的效能。最近看了一下 newrelic-python-agent 原始碼,這是檢視原始碼過程中的一些記錄。
目錄結構
newrelic 目錄結構如下:
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
可以列出所有命令:
$ 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/
目錄。
admin
admin 目錄是 newrelic-admin help 列出的命令指令碼所在目錄。
包含檔案如下:
$ 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
中定義的命令。
run_program
首先看下 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
進行。
newrelic run_program 初始化過程
以下為 flask 應用初始化過程,其它應用類似:
newrelic/admin/__init__.py main()
newrelic/admin/run_program.py
程式碼中會把newrelic/bootstrap/sitecustomize.py
新增到PYTHONPATH
,python 直譯器初始化的時候會自動 importPYTHONPATH
下存在的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,需要首先定義一個裝飾器函式。這將在每次呼叫修飾函式時呼叫。裝飾器函式需要使用四個位置引數:
- wrapped – The wrapped function which in turns needs to be called by your wrapper function.
- instance – The object to which the wrapped function was bound when it was called.
- args – The list of positional arguments supplied when the decorated function was called.
- kwargs – The dictionary of keyword arguments supplied when the decorated function was called.
具體使用參考文件吧。 文件地址
newrelic 原始碼仔細看下去,太…複雜了。下一篇再分析一個 flask 請求到結束探針工作的完整過程吧。
如果下一篇一直沒寫,那應該是看不下去了。
最後,感謝女朋友支援和包容,比❤️
也可以在公號輸入以下關鍵字獲取歷史文章:公號&小程式
| 設計模式
| 併發&協程