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 helpUsage: newrelic-admin command [options]Type 'newrelic-admin help'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 列出的命令脚本所在目录。
包含文件如下:
$ treeadmin├── __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.decoratordef pass_through(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs)@pass_throughdef function(): pass# 带参数的装饰器import wraptdef 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 请求到结束探针工作的完整过程吧。
如果下一篇一直没写,那应该是看不下去了。
最后,感谢女朋友支持和包容,比❤️
也可以在公号输入以下关键字获取历史文章:公号&小程序
| 设计模式
| 并发&协程