newrelic python agent 源碼分析-1

Newrelic 是APM(Application Performance Management)(應用性能管理/監控)解決方案提供商。項目中,一般用它來追蹤應用的性能。最近看了一下 newrelic-python-agent 源碼,這是查看源碼過程當中的一些記錄。

目錄結構

newrelic 目錄結構以下:html

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 能夠列出全部命令:python

$ 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 代碼能夠知道:web

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 列出的命令腳本所在目錄。

包含文件以下:django

$ 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 函數 是命令執行的入口。flask

__init__.py 文件中代碼bootstrap

load_internal_plugins()
load_external_plugins()

用來加載 _builtin_plugins 中定義的命令。小程序

run_program

首先看下 run_program 命令,這個命令使用方式以下:設計模式

newrelic-admin run-program your command

newrelic/admin/run_program.pyrun_program 函數有裝飾器 command,用來定義將命令以及相關說明添加到字典 _commandsapi

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 下存在的 sitecustomizeusercustomize 模塊。

以後的功能比較簡單,就是調用 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 應用初始化過程,其它應用相似:

  1. newrelic/admin/__init__.py main()
  2. newrelic/admin/run_program.py 代碼中會把 newrelic/bootstrap/sitecustomize.py 添加到 PYTHONPATH,python 解釋器初始化的時候會自動 import PYTHONPATH 下存在的 sitecustomizeusercustomize 模塊
  3. newrelic/bootstrap/sitecustomize.py 調用 newrelic.config.initialize()_setup_instrumentation() 函數被調用,_process_module_builtin會把須要 wrap 的包先添加到_import_hooks。
  4. newrelic/config.pysys.meta_path.insert(0, newrelic.api.import_hook.ImportHookFinder()) 執行
  5. newrelic/api/import_hook.py ImportHookFinder().find_model()
  6. newrelic/api/import_hook.py _ImportHookLoader() or _ImportHookChainedLoader()
  7. newrelic/api/import_hook.py _notify_import_hooks callablenewrelic/config _module_import_hook _instrument
  8. newrelic/hooks/framework_flask.py instrument_flask_app
  9. newrelic/api/web_transaction.py wrap_wsgi_application
  10. 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 請求到結束探針工做的完整過程吧。

最後,感謝女友支持和包容,比❤️

也能夠在公號輸入如下關鍵字獲取歷史文章:公號&小程序 | 設計模式 | 併發&協程

相關文章
相關標籤/搜索