cavisson-pythonagent 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
- cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
- cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
- cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
- cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
- pythonagent/__init__.py +22 -0
- pythonagent/agent/__init__.py +219 -0
- pythonagent/agent/internal/__init__.py +1 -0
- pythonagent/agent/internal/agent.py +1651 -0
- pythonagent/agent/internal/framesinfo.py +152 -0
- pythonagent/agent/internal/heap_dump.py +39 -0
- pythonagent/agent/internal/intercept_module.py +70 -0
- pythonagent/agent/internal/logs.py +122 -0
- pythonagent/agent/internal/metadata/__init__.py +0 -0
- pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
- pythonagent/agent/internal/proc_compat.py +75 -0
- pythonagent/agent/internal/profile.py +47 -0
- pythonagent/agent/internal/provider.py +83 -0
- pythonagent/agent/internal/thread_dump.py +85 -0
- pythonagent/agent/internal/udp.py +245 -0
- pythonagent/agent/internal/udp_message.py +800 -0
- pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
- pythonagent/agent/probes/Instrumentation/find.py +243 -0
- pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
- pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
- pythonagent/agent/probes/Instrumentation/parser.py +129 -0
- pythonagent/agent/probes/__init__.py +219 -0
- pythonagent/agent/probes/base.py +303 -0
- pythonagent/agent/probes/cache/__init__.py +51 -0
- pythonagent/agent/probes/cache/redis.py +119 -0
- pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
- pythonagent/agent/probes/coroutines/__init__.py +1 -0
- pythonagent/agent/probes/coroutines/asyncio.py +63 -0
- pythonagent/agent/probes/elasticdb/__init__.py +7 -0
- pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
- pythonagent/agent/probes/frameworks/__init__.py +27 -0
- pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
- pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
- pythonagent/agent/probes/frameworks/aisess.py +151 -0
- pythonagent/agent/probes/frameworks/asgi.py +340 -0
- pythonagent/agent/probes/frameworks/bottle.py +27 -0
- pythonagent/agent/probes/frameworks/cherry.py +25 -0
- pythonagent/agent/probes/frameworks/django.py +128 -0
- pythonagent/agent/probes/frameworks/falcon.py +21 -0
- pythonagent/agent/probes/frameworks/fastapi.py +35 -0
- pythonagent/agent/probes/frameworks/flask.py +30 -0
- pythonagent/agent/probes/frameworks/pyramid.py +56 -0
- pythonagent/agent/probes/frameworks/test.py +108 -0
- pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
- pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
- pythonagent/agent/probes/frameworks/wsgi.py +353 -0
- pythonagent/agent/probes/grpc/__init__.py +76 -0
- pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
- pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
- pythonagent/agent/probes/havoc/__init__.py +0 -0
- pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
- pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
- pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
- pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
- pythonagent/agent/probes/http/__init__.py +49 -0
- pythonagent/agent/probes/http/aiohttp_client.py +59 -0
- pythonagent/agent/probes/http/boto.py +12 -0
- pythonagent/agent/probes/http/httplib.py +110 -0
- pythonagent/agent/probes/http/httpx_client.py +116 -0
- pythonagent/agent/probes/http/requests.py +15 -0
- pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
- pythonagent/agent/probes/http/urllib3.py +16 -0
- pythonagent/agent/probes/langchain/__init__.py +21 -0
- pythonagent/agent/probes/langchain/base_tool.py +95 -0
- pythonagent/agent/probes/langchain/langchain_community.py +136 -0
- pythonagent/agent/probes/langchain/langchain_core.py +32 -0
- pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
- pythonagent/agent/probes/logging/__init__.py +106 -0
- pythonagent/agent/probes/message_brokers/__init__.py +4 -0
- pythonagent/agent/probes/message_brokers/pika.py +126 -0
- pythonagent/agent/probes/mongodb/__init__.py +6 -0
- pythonagent/agent/probes/mongodb/pymongo.py +286 -0
- pythonagent/agent/probes/openai/__init__.py +3 -0
- pythonagent/agent/probes/openai/openai.py +797 -0
- pythonagent/agent/probes/span.py +101 -0
- pythonagent/agent/probes/sql/__init__.py +13 -0
- pythonagent/agent/probes/sql/botocores3.py +51 -0
- pythonagent/agent/probes/sql/dbapi.py +285 -0
- pythonagent/agent/probes/sql/dynamodb.py +90 -0
- pythonagent/agent/probes/sql/mysql_connector.py +24 -0
- pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
- pythonagent/agent/probes/sql/mysqldb.py +43 -0
- pythonagent/agent/probes/sql/psycopg2.py +174 -0
- pythonagent/agent/probes/sql/pymysql.py +25 -0
- pythonagent/bootstrap/__init__.py +0 -0
- pythonagent/bootstrap/cav_gunicorn.py +26 -0
- pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
- pythonagent/bootstrap/run.py +47 -0
- pythonagent/bootstrap/sitecustomize.py +287 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
- pythonagent/config.py +279 -0
- pythonagent/find.py +72 -0
- pythonagent/find_mod_cls_name.py +54 -0
- pythonagent/lang.py +131 -0
- pythonagent/lib.py +91 -0
- pythonagent/main/__init__.py +0 -0
- pythonagent/main/pytrace/__init__.py +79 -0
- pythonagent/main/pytrace/commands/__init__.py +0 -0
- pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
- pythonagent/main/pytrace/commands/run.py +401 -0
- pythonagent/main/pytrace/pytrace.py +133 -0
- pythonagent/main/wsgi.py +6 -0
- pythonagent/main.py +27 -0
- pythonagent/run.py +46 -0
- pythonagent/sqins.py +8 -0
- pythonagent/test.py +73 -0
- pythonagent/utils.py +168 -0
- pythonagent/vendor/__init__.py +0 -0
- pythonagent/vendor/pympler/__init__.py +1 -0
- pythonagent/vendor/pympler/asizeof.py +2810 -0
- pythonagent/vendor/pympler/charts.py +62 -0
- pythonagent/vendor/pympler/classtracker.py +590 -0
- pythonagent/vendor/pympler/classtracker_stats.py +780 -0
- pythonagent/vendor/pympler/garbagegraph.py +80 -0
- pythonagent/vendor/pympler/mprofile.py +97 -0
- pythonagent/vendor/pympler/muppy.py +275 -0
- pythonagent/vendor/pympler/panels.py +115 -0
- pythonagent/vendor/pympler/process.py +238 -0
- pythonagent/vendor/pympler/py.typed +0 -0
- pythonagent/vendor/pympler/refbrowser.py +451 -0
- pythonagent/vendor/pympler/refgraph.py +350 -0
- pythonagent/vendor/pympler/summary.py +321 -0
- pythonagent/vendor/pympler/tracker.py +267 -0
- pythonagent/vendor/pympler/util/__init__.py +0 -0
- pythonagent/vendor/pympler/util/bottle.py +3809 -0
- pythonagent/vendor/pympler/util/compat.py +23 -0
- pythonagent/vendor/pympler/util/stringutils.py +77 -0
- pythonagent/vendor/pympler/web.py +346 -0
- pythonagent/vendor/werkzeug/__init__.py +20 -0
- pythonagent/vendor/werkzeug/_compat.py +228 -0
- pythonagent/vendor/werkzeug/_internal.py +473 -0
- pythonagent/vendor/werkzeug/_reloader.py +341 -0
- pythonagent/vendor/werkzeug/datastructures.py +3120 -0
- pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
- pythonagent/vendor/werkzeug/debug/console.py +218 -0
- pythonagent/vendor/werkzeug/debug/repr.py +297 -0
- pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
- pythonagent/vendor/werkzeug/exceptions.py +829 -0
- pythonagent/vendor/werkzeug/filesystem.py +64 -0
- pythonagent/vendor/werkzeug/formparser.py +584 -0
- pythonagent/vendor/werkzeug/http.py +1307 -0
- pythonagent/vendor/werkzeug/local.py +420 -0
- pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
- pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
- pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
- pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
- pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
- pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
- pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
- pythonagent/vendor/werkzeug/posixemulation.py +117 -0
- pythonagent/vendor/werkzeug/routing.py +2210 -0
- pythonagent/vendor/werkzeug/security.py +249 -0
- pythonagent/vendor/werkzeug/serving.py +1117 -0
- pythonagent/vendor/werkzeug/test.py +1123 -0
- pythonagent/vendor/werkzeug/testapp.py +241 -0
- pythonagent/vendor/werkzeug/urls.py +1138 -0
- pythonagent/vendor/werkzeug/useragents.py +202 -0
- pythonagent/vendor/werkzeug/utils.py +778 -0
- pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
- pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
- pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
- pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
- pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
- pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
- pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
- pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
- pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
- pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
- pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
- pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
- pythonagent/vendor/werkzeug/wsgi.py +1000 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Trick Python into loading the agent.
|
|
2
|
+
"""
|
|
3
|
+
from __future__ import unicode_literals
|
|
4
|
+
|
|
5
|
+
# ---------------------------------------------------------------------------
|
|
6
|
+
# sitecustomize chaining
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# WHY THIS EXISTS
|
|
9
|
+
#
|
|
10
|
+
# The install script puts the agent's site-packages directory on PYTHONPATH:
|
|
11
|
+
#
|
|
12
|
+
# export PYTHONPATH=${NDHOME}/python/site-packages:${PYTHONPATH}
|
|
13
|
+
#
|
|
14
|
+
# At interpreter startup, sys.path is built as:
|
|
15
|
+
# [script's own dir] + [PYTHONPATH entries, in order] + [stdlib / venv site-packages]
|
|
16
|
+
#
|
|
17
|
+
# That puts our directory at sys.path[1] — right after the script's own dir,
|
|
18
|
+
# and ahead of the application's virtualenv, ahead of any co-tool's install
|
|
19
|
+
# location, ahead of basically everything else on the machine.
|
|
20
|
+
#
|
|
21
|
+
# Python auto-imports a module named `sitecustomize` on every interpreter
|
|
22
|
+
# startup if one exists anywhere on sys.path — and it imports ONLY THE FIRST
|
|
23
|
+
# ONE IT FINDS. That auto-load is exactly what lets this agent instrument an
|
|
24
|
+
# application with zero code changes: no import statement, no app code
|
|
25
|
+
# edited, just install + set PYTHONPATH and the agent wires itself in before
|
|
26
|
+
# the app even starts running.
|
|
27
|
+
#
|
|
28
|
+
# But that same "found first, wins, no error if others are skipped" behavior
|
|
29
|
+
# is also what breaks other tools. Because our directory sits ahead of
|
|
30
|
+
# everything else, OUR sitecustomize.py wins the search every time — even on
|
|
31
|
+
# a machine where the monitored application (or a co-tool: Sentry,
|
|
32
|
+
# coverage.py, pytest-cov, virtualenv bootstrapping, etc.) ships its own
|
|
33
|
+
# sitecustomize.py for its own startup logic. Python does not merge them,
|
|
34
|
+
# does not warn, and does not fall through to the next one. Whichever
|
|
35
|
+
# sitecustomize loses the race is just silently never imported — its setup
|
|
36
|
+
# never runs. From the customer's point of view, installing this agent would
|
|
37
|
+
# look like it broke coverage reporting or killed Sentry init, with no error
|
|
38
|
+
# anywhere to explain why.
|
|
39
|
+
#
|
|
40
|
+
# So this agent cannot just let auto-load do its thing — it has to actively
|
|
41
|
+
# go find whatever sitecustomize.py *would* have been loaded if we weren't
|
|
42
|
+
# here, and run it ourselves, in full, before continuing with our own
|
|
43
|
+
# bootstrap. That's the chaining below.
|
|
44
|
+
#
|
|
45
|
+
# HOW IT WORKS
|
|
46
|
+
# 1. CPython adds the module to sys.modules BEFORE executing its body, so
|
|
47
|
+
# sys.modules['sitecustomize'] already points to our (partially-
|
|
48
|
+
# initialised) module object when this code runs.
|
|
49
|
+
# 2. We temporarily remove our bootstrap directory from sys.path so the
|
|
50
|
+
# next import search skips us and is forced to look further down
|
|
51
|
+
# sys.path — i.e. at wherever the app's or co-tool's own
|
|
52
|
+
# sitecustomize.py actually lives.
|
|
53
|
+
# 3. We drop 'sitecustomize' from sys.modules so `import sitecustomize`
|
|
54
|
+
# triggers a fresh file search instead of returning a cache hit (which
|
|
55
|
+
# would just hand back ourselves again).
|
|
56
|
+
# 4. We import the next sitecustomize — it runs in full, doing whatever
|
|
57
|
+
# setup it was originally supposed to do.
|
|
58
|
+
# 5. In `finally` we restore our directory in sys.path at its original
|
|
59
|
+
# position and re-register ourselves in sys.modules so the rest of this
|
|
60
|
+
# file (the agent bootstrap) completes normally, and so that for the
|
|
61
|
+
# rest of the process's lifetime, `sys.modules['sitecustomize']`
|
|
62
|
+
# resolves back to us, not the chained module.
|
|
63
|
+
import sys as _sys
|
|
64
|
+
import os as _os
|
|
65
|
+
|
|
66
|
+
_sc_this_dir = _os.path.dirname(_os.path.abspath(__file__))
|
|
67
|
+
_sc_this_dir_norm = _os.path.normcase(_sc_this_dir)
|
|
68
|
+
|
|
69
|
+
# Step 1 — keep a reference to ourselves (the partially-initialised module)
|
|
70
|
+
_sc_our_module = _sys.modules.get('sitecustomize')
|
|
71
|
+
|
|
72
|
+
# Step 2 — remove our directory from sys.path in-place so that any
|
|
73
|
+
# modifications the chained sitecustomize makes to sys.path are preserved.
|
|
74
|
+
_sc_our_idx = next(
|
|
75
|
+
(i for i, p in enumerate(_sys.path)
|
|
76
|
+
if _os.path.normcase(_os.path.abspath(p)) == _sc_this_dir_norm),
|
|
77
|
+
-1,
|
|
78
|
+
)
|
|
79
|
+
if _sc_our_idx >= 0:
|
|
80
|
+
_sys.path.pop(_sc_our_idx)
|
|
81
|
+
|
|
82
|
+
# Steps 3-4 — import the next sitecustomize (if any)
|
|
83
|
+
_sys.modules.pop('sitecustomize', None)
|
|
84
|
+
try:
|
|
85
|
+
import sitecustomize as _sc_chained # noqa: F401
|
|
86
|
+
except Exception:
|
|
87
|
+
pass # no other sitecustomize exists, or it raised — do not break the agent
|
|
88
|
+
finally:
|
|
89
|
+
# Step 5 — restore our directory and re-register ourselves
|
|
90
|
+
if _sc_our_idx >= 0:
|
|
91
|
+
_sys.path.insert(_sc_our_idx, _sc_this_dir)
|
|
92
|
+
if _sc_our_module is not None:
|
|
93
|
+
_sys.modules['sitecustomize'] = _sc_our_module
|
|
94
|
+
|
|
95
|
+
del _sys, _os, _sc_this_dir, _sc_this_dir_norm, _sc_our_module, _sc_our_idx
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# End sitecustomize chaining
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
import logging
|
|
101
|
+
import os
|
|
102
|
+
import sys
|
|
103
|
+
|
|
104
|
+
sitecustomize_dir = os.path.dirname(__file__)
|
|
105
|
+
pythonagent_dir = os.path.join(sitecustomize_dir, "pythonagent")
|
|
106
|
+
|
|
107
|
+
_bootstrapped_pid = None
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# /proc-based process helpers (no psutil)
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
def _read_proc_cmdline(pid="self"):
|
|
114
|
+
"""Return the argv list for *pid* by reading /proc/<pid>/cmdline.
|
|
115
|
+
Returns an empty list if the file cannot be read (non-Linux, no permission).
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
with open("/proc/{}/cmdline".format(pid), "rb") as fh:
|
|
119
|
+
data = fh.read()
|
|
120
|
+
if not data:
|
|
121
|
+
return []
|
|
122
|
+
# argv entries are NUL-separated; strip trailing NUL before splitting
|
|
123
|
+
return data.rstrip(b"\x00").split(b"\x00")
|
|
124
|
+
except (IOError, OSError):
|
|
125
|
+
return []
|
|
126
|
+
|
|
127
|
+
def _read_proc_comm(pid="self"):
|
|
128
|
+
"""Return the short process name from /proc/<pid>/comm (Linux only)."""
|
|
129
|
+
try:
|
|
130
|
+
with open("/proc/{}/comm".format(pid), "r") as fh:
|
|
131
|
+
return fh.read().strip().lower()
|
|
132
|
+
except (IOError, OSError):
|
|
133
|
+
return ""
|
|
134
|
+
|
|
135
|
+
def _cmdline_args():
|
|
136
|
+
"""Best-effort argv list (bytes decoded to str, lower-cased preserved separately)."""
|
|
137
|
+
raw = _read_proc_cmdline()
|
|
138
|
+
if raw:
|
|
139
|
+
try:
|
|
140
|
+
return [a.decode("utf-8", errors="replace") for a in raw]
|
|
141
|
+
except Exception:
|
|
142
|
+
return []
|
|
143
|
+
# Fallback: sys.argv (may differ from the real cmdline on some platforms)
|
|
144
|
+
return list(sys.argv)
|
|
145
|
+
|
|
146
|
+
def _cmdline_text(args):
|
|
147
|
+
return " ".join(args).lower()
|
|
148
|
+
|
|
149
|
+
def _process_name():
|
|
150
|
+
"""Short process name, lower-cased."""
|
|
151
|
+
comm = _read_proc_comm()
|
|
152
|
+
if comm:
|
|
153
|
+
return comm
|
|
154
|
+
# Fallback: basename of argv[0]
|
|
155
|
+
if sys.argv:
|
|
156
|
+
return os.path.basename(sys.argv[0]).lower()
|
|
157
|
+
return ""
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# uWSGI helpers
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
def _is_uwsgi_master():
|
|
164
|
+
"""Return True when running inside a uWSGI master process.
|
|
165
|
+
uwsgi.worker_id() returns 0 in the master and 1..N in workers.
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
import uwsgi
|
|
169
|
+
return uwsgi.worker_id() == 0
|
|
170
|
+
except (ImportError, AttributeError):
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Uvicorn helpers
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def _is_uvicorn_command(args, proc_name):
|
|
178
|
+
if proc_name == "uvicorn":
|
|
179
|
+
return True
|
|
180
|
+
lowered = [a.lower() for a in args]
|
|
181
|
+
if "uvicorn" in lowered:
|
|
182
|
+
return True
|
|
183
|
+
for i, arg in enumerate(lowered):
|
|
184
|
+
if arg == "-m" and i + 1 < len(lowered) and lowered[i + 1] == "uvicorn":
|
|
185
|
+
return True
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
def _has_uvicorn_supervisor_options(args):
|
|
189
|
+
for i, arg in enumerate(args):
|
|
190
|
+
lo = arg.lower()
|
|
191
|
+
if lo == "--reload":
|
|
192
|
+
return True
|
|
193
|
+
if lo in ("--workers",) or lo.startswith("--workers="):
|
|
194
|
+
return True
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
def _is_uvicorn_supervisor(args, proc_name):
|
|
198
|
+
return _is_uvicorn_command(args, proc_name) and _has_uvicorn_supervisor_options(args)
|
|
199
|
+
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
# One-shot bootstrap guard
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
def _bootstrap_agent_once(agent, cfg):
|
|
205
|
+
global _bootstrapped_pid
|
|
206
|
+
pid = os.getpid()
|
|
207
|
+
if _bootstrapped_pid == pid:
|
|
208
|
+
return
|
|
209
|
+
cfg.map_uwsgi_environ()
|
|
210
|
+
agent.configure()
|
|
211
|
+
agent.bootstrap()
|
|
212
|
+
_bootstrapped_pid = pid
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
# sys.path cleanup for old split-bootstrap layout
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
if not os.path.isdir(pythonagent_dir):
|
|
219
|
+
try:
|
|
220
|
+
sys.path.remove(sitecustomize_dir)
|
|
221
|
+
except ValueError:
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
# Main bootstrap
|
|
226
|
+
# ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
import pythonagent.agent as agent
|
|
230
|
+
from pythonagent import config as _config
|
|
231
|
+
|
|
232
|
+
args = _cmdline_args()
|
|
233
|
+
proc_name = _process_name()
|
|
234
|
+
cmd_text = _cmdline_text(args)
|
|
235
|
+
|
|
236
|
+
# --- gunicorn master: workers bootstrap via post_fork() in gunicorn cfg ---
|
|
237
|
+
gunicorn = "gunicorn" in cmd_text or proc_name == "gunicorn"
|
|
238
|
+
|
|
239
|
+
# --- uvicorn supervisor: child workers re-run sitecustomize via spawn ---
|
|
240
|
+
uvicorn_supervisor = _is_uvicorn_supervisor(args, proc_name)
|
|
241
|
+
|
|
242
|
+
# --- uWSGI master: workers bootstrap via @postfork below ---
|
|
243
|
+
uwsgi_master = _is_uwsgi_master()
|
|
244
|
+
|
|
245
|
+
if gunicorn or uvicorn_supervisor or uwsgi_master:
|
|
246
|
+
if gunicorn:
|
|
247
|
+
# Auto-bootstrap the agent in each gunicorn worker process after fork.
|
|
248
|
+
# Patch Worker.init_process at the class level in the master so every
|
|
249
|
+
# forked worker inherits the patch and runs bootstrap() before serving.
|
|
250
|
+
# This eliminates the need for a manual post_fork hook in gunicorn.conf.
|
|
251
|
+
try:
|
|
252
|
+
import gunicorn.workers.base as _gwb
|
|
253
|
+
|
|
254
|
+
_cav_orig_init_process = _gwb.Worker.init_process
|
|
255
|
+
|
|
256
|
+
def _cav_worker_init_process(self):
|
|
257
|
+
_bootstrap_agent_once(agent, _config)
|
|
258
|
+
return _cav_orig_init_process(self)
|
|
259
|
+
|
|
260
|
+
_gwb.Worker.init_process = _cav_worker_init_process
|
|
261
|
+
except Exception:
|
|
262
|
+
pass # gunicorn not installed or incompatible version — no-op
|
|
263
|
+
|
|
264
|
+
else:
|
|
265
|
+
_bootstrap_agent_once(agent, _config)
|
|
266
|
+
|
|
267
|
+
# Register uWSGI postfork hook so each worker bootstraps after fork.
|
|
268
|
+
# uwsgidecorators is only importable inside a uWSGI process.
|
|
269
|
+
try:
|
|
270
|
+
from uwsgidecorators import postfork as _uwsgi_postfork
|
|
271
|
+
|
|
272
|
+
@_uwsgi_postfork
|
|
273
|
+
def _cavisson_uwsgi_postfork():
|
|
274
|
+
try:
|
|
275
|
+
_bootstrap_agent_once(agent, _config)
|
|
276
|
+
except Exception:
|
|
277
|
+
pass
|
|
278
|
+
except (ImportError, AttributeError):
|
|
279
|
+
pass
|
|
280
|
+
|
|
281
|
+
except Exception:
|
|
282
|
+
logger = logging.getLogger("pythonagent.agent")
|
|
283
|
+
logger.exception("Exception in agent startup.")
|
|
284
|
+
finally:
|
|
285
|
+
pass
|
|
286
|
+
# appdynamics.agent.load_sitecustomize()
|
|
287
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modules": [
|
|
3
|
+
{
|
|
4
|
+
"moduleName": "run",
|
|
5
|
+
"moduleLevelMethod": [
|
|
6
|
+
{
|
|
7
|
+
"name": "http_callout",
|
|
8
|
+
"signature": "NA"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "first",
|
|
12
|
+
"signature": "NA"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "second",
|
|
16
|
+
"signature": "NA"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "db_callout",
|
|
20
|
+
"signature": "NA"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"classes": []
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Entry points
|
|
2
|
+
flask
|
|
3
|
+
django.core.handlers.wsgi
|
|
4
|
+
django.core.handlers.base
|
|
5
|
+
bottle
|
|
6
|
+
tornado.web
|
|
7
|
+
|
|
8
|
+
# HTTP exit calls
|
|
9
|
+
httplib
|
|
10
|
+
http.client
|
|
11
|
+
urllib3
|
|
12
|
+
requests
|
|
13
|
+
tornado.httpclient
|
|
14
|
+
boto.https_connection
|
|
15
|
+
|
|
16
|
+
# Logging
|
|
17
|
+
logging
|
|
18
|
+
|
|
19
|
+
# SQL exit calls
|
|
20
|
+
psycopg2
|
|
21
|
+
pymysql.connections
|
|
22
|
+
mysql.connector.connection
|
|
23
|
+
MySQLdb.connections
|
|
24
|
+
|
|
25
|
+
# MongoDB
|
|
26
|
+
pymongo
|
|
27
|
+
|
|
28
|
+
#REDIS
|
|
29
|
+
redis.connection
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modules": [
|
|
3
|
+
{
|
|
4
|
+
"moduleName": "run",
|
|
5
|
+
"moduleLevelMethod": [
|
|
6
|
+
{
|
|
7
|
+
"name": "http_callout",
|
|
8
|
+
"signature": "NA"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "first",
|
|
12
|
+
"signature": "NA"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "second",
|
|
16
|
+
"signature": "NA"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"classes": []
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"moduleName": "dynamodb",
|
|
23
|
+
"moduleLevelMethod": [
|
|
24
|
+
{
|
|
25
|
+
"name": "dynamodb_callout",
|
|
26
|
+
"signature": "NA"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"classes": []
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"moduleName": "s3",
|
|
33
|
+
"moduleLevelMethod": [
|
|
34
|
+
{
|
|
35
|
+
"name": "s3_callout",
|
|
36
|
+
"signature": "NA"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"classes": []
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Entry points
|
|
2
|
+
flask
|
|
3
|
+
django.core.handlers.wsgi
|
|
4
|
+
django.core.handlers.base
|
|
5
|
+
bottle
|
|
6
|
+
tornado.web
|
|
7
|
+
|
|
8
|
+
# HTTP exit calls
|
|
9
|
+
httplib
|
|
10
|
+
http.client
|
|
11
|
+
urllib3
|
|
12
|
+
requests
|
|
13
|
+
tornado.httpclient
|
|
14
|
+
boto.https_connection
|
|
15
|
+
|
|
16
|
+
# Logging
|
|
17
|
+
logging
|
|
18
|
+
|
|
19
|
+
# SQL exit calls
|
|
20
|
+
psycopg2
|
|
21
|
+
pymysql.connections
|
|
22
|
+
mysql.connector.connection
|
|
23
|
+
MySQLdb.connections
|
|
24
|
+
|
|
25
|
+
# MongoDB
|
|
26
|
+
pymongo
|
|
27
|
+
|
|
28
|
+
#REDIS
|
|
29
|
+
redis.connection
|
pythonagent/config.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
|
|
2
|
+
from __future__ import unicode_literals
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import configparser
|
|
6
|
+
cofig= configparser.RawConfigParser()
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from configparser import ConfigParser
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
ospath = os.environ.get('NDHOME')
|
|
13
|
+
path = ospath + '/python/config/ndsettings.conf'
|
|
14
|
+
except Exception as e:
|
|
15
|
+
print("ND HOME NOT SET")
|
|
16
|
+
raise e
|
|
17
|
+
|
|
18
|
+
#print("path of NDC config : ", path)
|
|
19
|
+
|
|
20
|
+
#configParser.read(path)
|
|
21
|
+
#path_for_insProfile = configParser.get('PYTHON_AGENT', 'path_profile')
|
|
22
|
+
|
|
23
|
+
myvars = {}
|
|
24
|
+
with open(path) as myfile:
|
|
25
|
+
for line in myfile:
|
|
26
|
+
name, var = line.partition("=")[::2]
|
|
27
|
+
myvars[name.strip()] = var
|
|
28
|
+
#print("myvars----------------------->",myvars)
|
|
29
|
+
#TIER = myvars['tier'].strip('\n')
|
|
30
|
+
#SERVER = myvars['server'].strip('\n')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
int_or_none = lambda v: int(v) if v != '' else None
|
|
36
|
+
on_off = lambda v: v.lower() in ('on', 'true', 'yes', 'y', 't', '1')
|
|
37
|
+
comma_seperated_list = lambda v: v.replace(' ', '').split(',')
|
|
38
|
+
|
|
39
|
+
keys = lambda d: d.keys()
|
|
40
|
+
values = lambda d: d.values()
|
|
41
|
+
# Configuration Options
|
|
42
|
+
|
|
43
|
+
_CONFIG_OPTIONS_BY_SECTION = {
|
|
44
|
+
|
|
45
|
+
'agent': {
|
|
46
|
+
'app': ('APP_NAME', None),
|
|
47
|
+
'tier': ('TIER_NAME', None),
|
|
48
|
+
'node': ('NODE_NAME', None),
|
|
49
|
+
'dir': ('DIR', None),
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
'wsgi': {
|
|
53
|
+
'script': ('WSGI_SCRIPT_ALIAS', None),
|
|
54
|
+
'callable': ('WSGI_CALLABLE_OBJECT', None),
|
|
55
|
+
'module': ('WSGI_MODULE', None),
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
'asgi': {
|
|
59
|
+
'module': ('ASGI_MODULE', None),
|
|
60
|
+
'callable': ('ASGI_CALLABLE_OBJECT', None),
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
'log': {
|
|
64
|
+
'dir': ('LOGS_DIR', None),
|
|
65
|
+
'level': ('LOGGING_LEVEL', None),
|
|
66
|
+
'debugging': ('DEBUG_LOG', on_off)
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
'proxy': {
|
|
70
|
+
'proxymode': ('PROXY_MODE', None),
|
|
71
|
+
'proxycontype': ('PROXY_CON_TYPE', None),
|
|
72
|
+
'proxyip': ('PROXY_IP', None),
|
|
73
|
+
'proxyport': ('PROXY_PORT', None),
|
|
74
|
+
'sopath': ('SO_PATH', None),
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Defaults ###########
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
CONFIG_FILE = ''
|
|
83
|
+
|
|
84
|
+
# Agent
|
|
85
|
+
APP_NAME = 'CavAgent'
|
|
86
|
+
#TIER_NAME = 'test_tier'
|
|
87
|
+
#NODE_NAME = 'test'
|
|
88
|
+
DIR = ospath + '/python/hooksettings'
|
|
89
|
+
try:
|
|
90
|
+
TIER = myvars['tier'].strip('\n')
|
|
91
|
+
SERVER = myvars['server'].strip('\n')
|
|
92
|
+
except:
|
|
93
|
+
TIER = "Default"
|
|
94
|
+
SERVER = "Default"
|
|
95
|
+
|
|
96
|
+
# Logging
|
|
97
|
+
LOGS_DIR = ospath+'/python/logs'
|
|
98
|
+
print("LOGS_DIR", LOGS_DIR)
|
|
99
|
+
|
|
100
|
+
if "CAV_APP_AGENT_LOGGINGLEVEL" in os.environ:
|
|
101
|
+
LOGGING_LEVEL = os.environ.get('CAV_APP_AGENT_LOGGINGLEVEL')
|
|
102
|
+
else:
|
|
103
|
+
LOGGING_LEVEL = "WARNING"
|
|
104
|
+
|
|
105
|
+
DEBUG_LOG = False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# WSGI
|
|
109
|
+
WSGI_MODULE = ''
|
|
110
|
+
#WSGI_SCRIPT_ALIAS = '/home/cavisson/shop/my-shop/wsgi_example_app.py'
|
|
111
|
+
#WSGI_SCRIPT_ALIAS = input("Enter the absolute path of WSGI application script. (command to get 'readlink -f <script_path.py>') : ")
|
|
112
|
+
WSGI_SCRIPT_ALIAS = ''
|
|
113
|
+
WSGI_CALLABLE_OBJECT = ''
|
|
114
|
+
|
|
115
|
+
# ASGI
|
|
116
|
+
ASGI_MODULE = ''
|
|
117
|
+
ASGI_CALLABLE_OBJECT = ''
|
|
118
|
+
|
|
119
|
+
# Controller
|
|
120
|
+
CONTROLLER_HOST = '10.20.0.85'
|
|
121
|
+
CONTROLLER_PORT = '7892'
|
|
122
|
+
SSL_ENABLED = False
|
|
123
|
+
ACCOUNT_NAME = ''
|
|
124
|
+
ACCOUNT_ACCESS_KEY = ''
|
|
125
|
+
HTTP_PROXY_HOST = '10.20.0.79'
|
|
126
|
+
HTTP_PROXY_PORT = '1112'
|
|
127
|
+
HTTP_PROXY_USER = ''
|
|
128
|
+
HTTP_PROXY_PASSWORD_FILE = ''
|
|
129
|
+
'''
|
|
130
|
+
# Proxy Runtime
|
|
131
|
+
PROXY_RUN_DIR = ''
|
|
132
|
+
|
|
133
|
+
# Proxy Control Service
|
|
134
|
+
PROXY_CONTROL_PATH = ''
|
|
135
|
+
PROXY_STARTUP_READ_TIMEOUT_MS = 2000
|
|
136
|
+
PROXY_STARTUP_INITIAL_RETRY_DELAY_MS = 5000
|
|
137
|
+
PROXY_STARTUP_MAX_RETRY_DELAY_MS = 300000
|
|
138
|
+
|
|
139
|
+
# Config Service
|
|
140
|
+
PROXY_CONFIG_SOCKET_NAME = '0'
|
|
141
|
+
CONFIG_SERVICE_RELOAD_INTERVAL_MS = 5000
|
|
142
|
+
CONFIG_SERVICE_MAX_RETRIES = 3
|
|
143
|
+
|
|
144
|
+
# Transaction Service
|
|
145
|
+
PROXY_INFO_SOCKET_NAME = '0'
|
|
146
|
+
PROXY_REPORTING_SOCKET_NAME = '1'
|
|
147
|
+
BT_INFO_REQUEST_TIMEOUT_MS = 100
|
|
148
|
+
|
|
149
|
+
# Snapshot Service
|
|
150
|
+
INCLUDE_AGENT_FRAMES = False
|
|
151
|
+
SNAPSHOT_PROFILER_INTERVAL_MS = 10
|
|
152
|
+
EXIT_CALL_DETAILS_LENGTH = 100
|
|
153
|
+
FORCED_SNAPSHOT_INTERVAL = 10
|
|
154
|
+
|
|
155
|
+
# Transaction Monitor Service
|
|
156
|
+
BT_MAX_DURATION_MS = 30 * 60 * 1000
|
|
157
|
+
BT_ABANDON_THRESHOLD_MULTIPLIER = 6
|
|
158
|
+
|
|
159
|
+
# EUM
|
|
160
|
+
EUM_DISABLE_COOKIE = False
|
|
161
|
+
EUM_USER_AGENT_WHITELIST = ['Mozilla', 'Opera', 'WebKit', 'Nokia']
|
|
162
|
+
'''
|
|
163
|
+
#RUNNING_MODE = 0
|
|
164
|
+
|
|
165
|
+
# Approach 7: mapping from uWSGI native env vars to Cavisson CAV_* equivalents.
|
|
166
|
+
# uWSGI exposes its own config as UWSGI_* env vars when started via env vars.
|
|
167
|
+
# UWSGI_WSGI maps to --wsgi-file; UWSGI_MODULE maps to --module; UWSGI_CALLABLE to --callable.
|
|
168
|
+
_UWSGI_ENV_MAP = {
|
|
169
|
+
'UWSGI_MODULE': 'CAV_WSGI_MODULE',
|
|
170
|
+
'UWSGI_WSGI': 'CAV_WSGI_SCRIPT_ALIAS',
|
|
171
|
+
'UWSGI_CALLABLE': 'CAV_WSGI_CALLABLE_OBJECT',
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def map_uwsgi_environ():
|
|
176
|
+
"""Translate UWSGI_* env vars into CAV_* equivalents, without overwriting
|
|
177
|
+
values the operator has already set explicitly via CAV_* vars."""
|
|
178
|
+
for uwsgi_var, cav_var in _UWSGI_ENV_MAP.items():
|
|
179
|
+
if uwsgi_var in os.environ and cav_var not in os.environ:
|
|
180
|
+
os.environ[cav_var] = os.environ[uwsgi_var]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def validate_config(config):
|
|
184
|
+
|
|
185
|
+
return True
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def parse_environ(environ=None, prefix='CAV_'):
|
|
189
|
+
|
|
190
|
+
logger = logging.getLogger('pythonagent.agent')
|
|
191
|
+
environ = environ if environ is not None else os.environ
|
|
192
|
+
|
|
193
|
+
config = {}
|
|
194
|
+
config_file = environ.get('CAV_CONFIG_FILE')
|
|
195
|
+
|
|
196
|
+
if config_file:
|
|
197
|
+
config = parse_config_file(config_file)
|
|
198
|
+
|
|
199
|
+
#environ['SO_PATH'] = config['SO_PATH']
|
|
200
|
+
option_descrs = {}
|
|
201
|
+
|
|
202
|
+
for options in values(_CONFIG_OPTIONS_BY_SECTION):
|
|
203
|
+
for name, handler in values(options):
|
|
204
|
+
option_descrs[prefix + name] = (name, handler)
|
|
205
|
+
|
|
206
|
+
for option in keys(environ):
|
|
207
|
+
if option not in option_descrs:
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
name, handler = option_descrs[option]
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
value = environ[option]
|
|
214
|
+
if handler:
|
|
215
|
+
value = handler(value)
|
|
216
|
+
config[name] = value
|
|
217
|
+
except:
|
|
218
|
+
logger.exception('ignoring %s from environment, parsing value caused exception', option)
|
|
219
|
+
|
|
220
|
+
return config
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def parse_config_file(filename):
|
|
224
|
+
return None
|
|
225
|
+
logger = logging.getLogger('pythonagent.agent')
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
config = {}
|
|
229
|
+
parser = ConfigParser()
|
|
230
|
+
|
|
231
|
+
with open(filename) as fp:
|
|
232
|
+
parser.read_file(fp)
|
|
233
|
+
|
|
234
|
+
for section_name in parser.sections():
|
|
235
|
+
try:
|
|
236
|
+
options_map = _CONFIG_OPTIONS_BY_SECTION[section_name]
|
|
237
|
+
except KeyError: # Unknown section
|
|
238
|
+
logger.warning('%s: skipping unrecognized section [%s]', filename, section_name)
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
for option_name in parser.options(section_name):
|
|
242
|
+
try:
|
|
243
|
+
env_name, handler = options_map[option_name]
|
|
244
|
+
|
|
245
|
+
value = parser.get(section_name, option_name)
|
|
246
|
+
if handler:
|
|
247
|
+
value = handler(value)
|
|
248
|
+
|
|
249
|
+
config[env_name] = value
|
|
250
|
+
except KeyError: # Unknown option
|
|
251
|
+
logger.warning('%s: skipping unrecognized option %r of section [%s]',
|
|
252
|
+
filename, option_name, section_name)
|
|
253
|
+
except: # Other errors
|
|
254
|
+
logger.exception('%s: parsing value for option %r of section [%s] raised exception',
|
|
255
|
+
filename, option_name, section_name)
|
|
256
|
+
|
|
257
|
+
return config
|
|
258
|
+
except:
|
|
259
|
+
logger.exception('Parsing config file failed.')
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def merge(config):
|
|
263
|
+
|
|
264
|
+
#mod = globals()
|
|
265
|
+
#mod.update(config)
|
|
266
|
+
update_computed_defaults()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def update_computed_defaults():
|
|
270
|
+
global LOGS_DIR, PROXY_CONTROL_PATH, PROXY_RUN_DIR
|
|
271
|
+
|
|
272
|
+
PROXY_RUN_DIR = os.path.join(DIR, 'run')
|
|
273
|
+
|
|
274
|
+
#if not PROXY_CONTROL_PATH:
|
|
275
|
+
# PROXY_CONTROL_PATH = os.path.join(PROXY_RUN_DIR, 'comm')
|
|
276
|
+
|
|
277
|
+
if not LOGS_DIR:
|
|
278
|
+
LOGS_DIR = os.path.join(DIR, 'logs')
|
|
279
|
+
|