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,340 @@
|
|
|
1
|
+
from ..base import ExitCallInterceptor
|
|
2
|
+
from pythonagent.agent.probes.Instrumentation.parser import create_method_array
|
|
3
|
+
from pythonagent.agent.probes.Instrumentation.new_parser import create_method_list
|
|
4
|
+
|
|
5
|
+
# from pythonagent.agent.probes.havoc.havoc_manager import NDNetHavocMonitor
|
|
6
|
+
from pythonagent.utils import get_current_timestamp_in_ms, get_current_timestamp_in_us, get_validated_duration,get_current_thread_time_in_us
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import configparser
|
|
10
|
+
import time
|
|
11
|
+
import threading
|
|
12
|
+
import sysconfig
|
|
13
|
+
import warnings
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
# Thread-local flag: prevents _custom_method from re-entering itself when an
|
|
17
|
+
# instrumented method (e.g. logging.Logger.debug) is called from within the
|
|
18
|
+
# wrapper. Without this guard a complete-AD profile that includes stdlib logger
|
|
19
|
+
# methods causes infinite recursion → C-stack overflow → SIGSEGV.
|
|
20
|
+
_nd_instr_active = threading.local()
|
|
21
|
+
|
|
22
|
+
# ALL Python data-model (dunder) methods — must never be instrumented.
|
|
23
|
+
# Wrapping operator methods (__or__, __and__, etc.) corrupts enum/flag
|
|
24
|
+
# semantics (Crash 2). Wrapping __getattr__/__getattribute__ causes
|
|
25
|
+
# infinite recursion on every attribute access.
|
|
26
|
+
_DUNDER_SKIP = frozenset([
|
|
27
|
+
# attribute access
|
|
28
|
+
'__getattr__', '__getattribute__', '__setattr__', '__delattr__',
|
|
29
|
+
# descriptor protocol
|
|
30
|
+
'__get__', '__set__', '__delete__',
|
|
31
|
+
# object lifecycle and class creation
|
|
32
|
+
'__new__', '__init__', '__del__', '__call__',
|
|
33
|
+
'__init_subclass__', '__class_getitem__', '__instancecheck__', '__subclasscheck__',
|
|
34
|
+
# string representations
|
|
35
|
+
'__repr__', '__str__', '__format__', '__bytes__',
|
|
36
|
+
# comparison and hashing
|
|
37
|
+
'__eq__', '__ne__', '__lt__', '__le__', '__gt__', '__ge__', '__hash__',
|
|
38
|
+
# boolean / numeric type conversions
|
|
39
|
+
'__bool__', '__int__', '__float__', '__complex__', '__index__',
|
|
40
|
+
'__round__', '__trunc__', '__floor__', '__ceil__', '__abs__', '__neg__', '__pos__',
|
|
41
|
+
# arithmetic operators
|
|
42
|
+
'__add__', '__radd__', '__iadd__',
|
|
43
|
+
'__sub__', '__rsub__', '__isub__',
|
|
44
|
+
'__mul__', '__rmul__', '__imul__',
|
|
45
|
+
'__truediv__', '__rtruediv__', '__itruediv__',
|
|
46
|
+
'__floordiv__', '__rfloordiv__', '__ifloordiv__',
|
|
47
|
+
'__mod__', '__rmod__', '__imod__',
|
|
48
|
+
'__pow__', '__rpow__', '__ipow__',
|
|
49
|
+
'__divmod__', '__rdivmod__',
|
|
50
|
+
# bitwise / logical operators — caused Crash 2 (re.I | re.ASCII)
|
|
51
|
+
'__or__', '__ror__', '__ior__',
|
|
52
|
+
'__and__', '__rand__', '__iand__',
|
|
53
|
+
'__xor__', '__rxor__', '__ixor__',
|
|
54
|
+
'__lshift__', '__rlshift__', '__ilshift__',
|
|
55
|
+
'__rshift__', '__rrshift__', '__irshift__',
|
|
56
|
+
'__invert__',
|
|
57
|
+
# container protocol
|
|
58
|
+
'__len__', '__length_hint__', '__iter__', '__reversed__', '__next__',
|
|
59
|
+
'__contains__', '__getitem__', '__setitem__', '__delitem__', '__missing__',
|
|
60
|
+
# context managers
|
|
61
|
+
'__enter__', '__exit__', '__aenter__', '__aexit__',
|
|
62
|
+
# async iterators / generators
|
|
63
|
+
'__await__', '__aiter__', '__anext__',
|
|
64
|
+
# pickle / copy
|
|
65
|
+
'__reduce__', '__reduce_ex__', '__copy__', '__deepcopy__',
|
|
66
|
+
# misc
|
|
67
|
+
'__sizeof__', '__dir__',
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _build_stdlib_realpath():
|
|
72
|
+
p = sysconfig.get_path('stdlib')
|
|
73
|
+
return os.path.realpath(p) if p else None
|
|
74
|
+
|
|
75
|
+
_STDLIB_REALPATH = _build_stdlib_realpath()
|
|
76
|
+
|
|
77
|
+
# Fallback prefix set used when sysconfig cannot determine the stdlib path.
|
|
78
|
+
_FALLBACK_STDLIB_NAMES = frozenset([
|
|
79
|
+
'logging', 'os', 'sys', 'io', 'abc', 'threading', 'collections',
|
|
80
|
+
'functools', 'importlib', 'types', 'traceback', 'inspect',
|
|
81
|
+
'contextlib', 'weakref', 'copyreg', '_io', '_thread',
|
|
82
|
+
're', 'enum', 'email', 'html', 'xml',
|
|
83
|
+
'json', 'csv', 'hashlib', 'hmac', 'ssl', 'socket', 'select',
|
|
84
|
+
'struct', 'codecs', 'string', 'textwrap', 'unicodedata',
|
|
85
|
+
'pythonagent',
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _is_stdlib_module(mod):
|
|
90
|
+
"""Return True if mod is from the Python standard library or is a built-in."""
|
|
91
|
+
mod_file = getattr(mod, '__file__', None)
|
|
92
|
+
if mod_file is None:
|
|
93
|
+
return True # built-in C extension — no file, always skip
|
|
94
|
+
if _STDLIB_REALPATH:
|
|
95
|
+
return os.path.realpath(mod_file).startswith(_STDLIB_REALPATH)
|
|
96
|
+
return mod.__name__.split('.')[0] in _FALLBACK_STDLIB_NAMES
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Top-level packages that are pure protocol/serialization/transport-layer
|
|
100
|
+
# plumbing for libraries that already have a dedicated exit-call probe
|
|
101
|
+
# (pymongo, requests, httpx, aiohttp, ...). Generically instrumenting these
|
|
102
|
+
# adds no tracing value -- they're never application/business logic -- and is
|
|
103
|
+
# actively harmful: their calls happen nested *inside* a DB/HTTP exit call's
|
|
104
|
+
# own begin/end window (e.g. bson encoding a query inside a pymongo command,
|
|
105
|
+
# or a TLS handshake inside a requests/httpx call), and interleaving extra
|
|
106
|
+
# native-SDK method_entry/method_exit calls in there corrupts the exit call's
|
|
107
|
+
# own callout ("T record") tracking. Excluding them here loses nothing: the
|
|
108
|
+
# dedicated probe for the library that actually uses them still captures the
|
|
109
|
+
# real DB/HTTP call.
|
|
110
|
+
_PLUMBING_PACKAGE_DENYLIST = frozenset([
|
|
111
|
+
'bson', # pymongo's BSON serialization
|
|
112
|
+
'cryptography', # TLS, used by requests/urllib3/httpx/pymongo
|
|
113
|
+
'dns', # dnspython -- hostname/SRV resolution (pymongo etc.)
|
|
114
|
+
'h11', # HTTP/1.1 protocol implementation (httpx/httpcore)
|
|
115
|
+
'httpcore', # httpx's transport layer
|
|
116
|
+
'attr', # attrs metaprogramming, used by dns/httpx/etc.
|
|
117
|
+
'anyio', # async compat layer (httpx/starlette)
|
|
118
|
+
'sniffio', # async library detection (anyio/httpx)
|
|
119
|
+
'multidict', # used by aiohttp/yarl
|
|
120
|
+
'yarl', # URL parsing (aiohttp)
|
|
121
|
+
'frozenlist', # used by aiohttp
|
|
122
|
+
'aiosignal', # used by aiohttp
|
|
123
|
+
'propcache', # used by yarl/aiohttp
|
|
124
|
+
'charset_normalizer', # encoding detection (requests)
|
|
125
|
+
'idna', # internationalized domain names (requests/urllib3/dns)
|
|
126
|
+
'certifi', # CA bundle (requests/urllib3)
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class MethodInterceptor(ExitCallInterceptor):
|
|
131
|
+
|
|
132
|
+
def _custom_method(self, original_method, *args, **kwargs):
|
|
133
|
+
# Reentrancy guard: prevents _custom_method from re-entering itself
|
|
134
|
+
# when the agent's OWN bookkeeping code (self.agent.logger.*,
|
|
135
|
+
# havoc_monitor.*, method_entry/method_exit) ends up calling back into
|
|
136
|
+
# an instrumented method. It must NOT cover the call to the real
|
|
137
|
+
# wrapped method below -- that's where the application's own nested
|
|
138
|
+
# calls happen (e.g. a Flask view function calling another
|
|
139
|
+
# instrumented helper), and those need to be measured normally. See
|
|
140
|
+
# _custom_method_inner for where the guard is actually held.
|
|
141
|
+
if getattr(_nd_instr_active, 'active', False):
|
|
142
|
+
return original_method(*args, **kwargs)
|
|
143
|
+
return self._custom_method_inner(original_method, *args, **kwargs)
|
|
144
|
+
|
|
145
|
+
def _custom_method_inner(self, original_method, *args, **kwargs):
|
|
146
|
+
bt = self.bt
|
|
147
|
+
before_time = 0
|
|
148
|
+
cpu_time_before = 0
|
|
149
|
+
cpu_time = 0
|
|
150
|
+
|
|
151
|
+
if hasattr(self.cls, "__module__"): # Class Level Method
|
|
152
|
+
fqmforentry = self.cls.__module__ + "." + self.cls.__name__ + "." + str(original_method.__name__)
|
|
153
|
+
else: # Module Level Method
|
|
154
|
+
fqmforentry = self.cls.__name__ + "." + original_method.__name__
|
|
155
|
+
|
|
156
|
+
if bt is not None:
|
|
157
|
+
# Guard is held ONLY around the agent's own entry-side bookkeeping,
|
|
158
|
+
# not around the real method call below.
|
|
159
|
+
_nd_instr_active.active = True
|
|
160
|
+
try:
|
|
161
|
+
try:
|
|
162
|
+
from pythonagent.agent.probes.havoc.havoc_manager import NDNetHavocMonitor, NDHavocException
|
|
163
|
+
havoc_monitor = NDNetHavocMonitor.get_instance()
|
|
164
|
+
before_time = get_current_timestamp_in_us()
|
|
165
|
+
cpu_time_before = get_current_thread_time_in_us()
|
|
166
|
+
if self.agent.logger.isEnabledFor(10):
|
|
167
|
+
self.agent.logger.debug("calling method entry bt %s fqm %s start_time %s", bt, fqmforentry, before_time)
|
|
168
|
+
self.agent.method_entry(bt, fqmforentry, before_time)
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
self.agent.logger.exception('exception in MethodInterceptor._custom_method entry part: {}'.format(e))
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
context = self.agent.get_transaction_context()
|
|
175
|
+
btname = context.btname if context else None
|
|
176
|
+
req_url = context.url_path if context else None
|
|
177
|
+
havoc_monitor.apply_method_call_delay(fqmforentry, req_url, btname)
|
|
178
|
+
havoc_monitor.apply_method_invocation_failure(fqmforentry, req_url, btname)
|
|
179
|
+
|
|
180
|
+
except Exception as e:
|
|
181
|
+
if isinstance(e, NDHavocException):
|
|
182
|
+
try:
|
|
183
|
+
cpu_time_after = get_current_thread_time_in_us()
|
|
184
|
+
after_time = get_current_timestamp_in_us()
|
|
185
|
+
cpu_time = cpu_time_after - cpu_time_before
|
|
186
|
+
duration = get_validated_duration(before_time, after_time, fqmforentry)
|
|
187
|
+
self.agent.logger.debug("calling method exit for havoc bt {} fqm {} duration {} cpu_time {} start_time {}".format(bt, fqmforentry, duration, cpu_time, before_time))
|
|
188
|
+
self.agent.set_current_status_code(503)
|
|
189
|
+
self.agent.method_exit(bt, fqmforentry, duration, cpu_time, before_time)
|
|
190
|
+
except Exception as exit_exception:
|
|
191
|
+
self.agent.logger.exception('exception in MethodInterceptor._custom_method havoc exit part: {}'.format(exit_exception))
|
|
192
|
+
raise
|
|
193
|
+
else:
|
|
194
|
+
self.agent.logger.debug("Non-Havoc Exception {}".format(e))
|
|
195
|
+
finally:
|
|
196
|
+
_nd_instr_active.active = False
|
|
197
|
+
|
|
198
|
+
# Guard is OFF here: this is the actual application call, which may
|
|
199
|
+
# itself call other instrumented methods -- those must be measured.
|
|
200
|
+
response = original_method(*args, **kwargs)
|
|
201
|
+
|
|
202
|
+
_nd_instr_active.active = True
|
|
203
|
+
try:
|
|
204
|
+
try:
|
|
205
|
+
cpu_time_after = get_current_thread_time_in_us()
|
|
206
|
+
after_time = get_current_timestamp_in_us()
|
|
207
|
+
cpu_time = cpu_time_after - cpu_time_before
|
|
208
|
+
duration = get_validated_duration(before_time, after_time, fqmforentry)
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
self.agent.logger.exception('exception in MethodInterceptor._custom_method exit initial part: {}'.format(e))
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
self.agent.logger.debug("calling method exit for native bt {} fqm {} duration {} cpu_time {} start_time {}".format(bt, fqmforentry, duration, cpu_time, before_time))
|
|
215
|
+
self.agent.method_exit(bt, fqmforentry, duration, cpu_time, before_time)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
self.agent.logger.exception('exception in MethodInterceptor._custom_method exit later part: {}'.format(e))
|
|
218
|
+
finally:
|
|
219
|
+
_nd_instr_active.active = False
|
|
220
|
+
|
|
221
|
+
else:
|
|
222
|
+
if self.agent.logger.isEnabledFor(10):
|
|
223
|
+
self.agent.logger.debug('MethodInterceptor._custom_method: no active BT for fqm %s, calling through uninstrumented', fqmforentry)
|
|
224
|
+
response = original_method(*args, **kwargs)
|
|
225
|
+
|
|
226
|
+
return response
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _sync_flask_view_functions(mod, agent):
|
|
230
|
+
"""After patching mod.* attributes, update Flask's view_functions to point to the wrappers.
|
|
231
|
+
|
|
232
|
+
Flask stores direct function-object references in app.view_functions at route-registration
|
|
233
|
+
time. setattr(mod, name, wrapper) replaces the module attribute but Flask's routing table
|
|
234
|
+
still holds the original. This walk finds Flask app objects in the module and re-points
|
|
235
|
+
their view_functions entries to the now-patched module attributes.
|
|
236
|
+
|
|
237
|
+
Safety check: this is called from intercept_instrumented_method() for every
|
|
238
|
+
instrumented module regardless of framework, so it must be a strict no-op
|
|
239
|
+
for apps that don't use Flask at all -- both when the flask package isn't
|
|
240
|
+
installed, and when it's installed (e.g. as an unused transitive
|
|
241
|
+
dependency) but never actually imported/used by this process.
|
|
242
|
+
"""
|
|
243
|
+
if 'flask' not in sys.modules:
|
|
244
|
+
return
|
|
245
|
+
try:
|
|
246
|
+
from flask import Flask
|
|
247
|
+
except ImportError:
|
|
248
|
+
return
|
|
249
|
+
try:
|
|
250
|
+
for attr_val in vars(mod).values():
|
|
251
|
+
if not isinstance(attr_val, Flask):
|
|
252
|
+
continue
|
|
253
|
+
for endpoint, orig_func in list(attr_val.view_functions.items()):
|
|
254
|
+
if getattr(orig_func, '__module__', None) != mod.__name__:
|
|
255
|
+
continue
|
|
256
|
+
patched = getattr(mod, orig_func.__name__, None)
|
|
257
|
+
if patched is not None and patched is not orig_func:
|
|
258
|
+
attr_val.view_functions[endpoint] = patched
|
|
259
|
+
if agent.logger.isEnabledFor(10):
|
|
260
|
+
agent.logger.debug(
|
|
261
|
+
"Synced Flask view_functions[%s] to patched wrapper", endpoint)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
agent.logger.warning("Could not sync Flask view_functions for %s: %s", mod.__name__, e)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def intercept_instrumented_method(agent, mod):
|
|
267
|
+
|
|
268
|
+
custom_methods_to_instrument = agent.custom_methods_to_instrument
|
|
269
|
+
package_and_module = mod.__name__
|
|
270
|
+
|
|
271
|
+
# Never instrument stdlib modules: their methods are called internally by
|
|
272
|
+
# _custom_method (logger, enum operators, etc.) and wrapping them causes
|
|
273
|
+
# crashes ranging from infinite recursion (Crash 1) to enum ValueError (Crash 2).
|
|
274
|
+
if _is_stdlib_module(mod):
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
# Also skip the agent's own internals regardless of install path.
|
|
278
|
+
if package_and_module.startswith('pythonagent'):
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
# Skip known protocol/serialization plumbing that's a dependency of a
|
|
282
|
+
# library with its own dedicated exit-call probe -- see
|
|
283
|
+
# _PLUMBING_PACKAGE_DENYLIST for why.
|
|
284
|
+
if package_and_module.split('.')[0] in _PLUMBING_PACKAGE_DENYLIST:
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
for item in custom_methods_to_instrument:
|
|
288
|
+
if not item.startswith(package_and_module + '.'):
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
class_and_method = item[len(package_and_module)+1:]
|
|
292
|
+
split = class_and_method.split(".")
|
|
293
|
+
if len(split) > 1: # Class Level Method
|
|
294
|
+
class_name = split[0]
|
|
295
|
+
method_name = split[1]
|
|
296
|
+
if method_name in _DUNDER_SKIP:
|
|
297
|
+
continue
|
|
298
|
+
try:
|
|
299
|
+
# Suppress deprecation/runtime warnings emitted by getattr on legacy
|
|
300
|
+
# third-party classes (e.g. cryptography's Blowfish, IDEA, CAST5).
|
|
301
|
+
with warnings.catch_warnings():
|
|
302
|
+
warnings.simplefilter("ignore")
|
|
303
|
+
cls = getattr(mod, class_name)
|
|
304
|
+
MethodInterceptor(agent, cls).attach(method_name, wrapper_func=None, patched_method_name='_custom_method')
|
|
305
|
+
except AttributeError:
|
|
306
|
+
# Profile depth may be off by one: class_name might be a sub-module that
|
|
307
|
+
# hasn't been imported into the parent's namespace yet (e.g. the instrprof.json
|
|
308
|
+
# records module=pkg, class=submod, method=ActualClass instead of
|
|
309
|
+
# module=pkg.submod, class=ActualClass, method=actual_method).
|
|
310
|
+
# Try resolving pkg.submod from sys.modules and shift the path down one level.
|
|
311
|
+
sub_mod = sys.modules.get(package_and_module + '.' + class_name)
|
|
312
|
+
if sub_mod is not None and len(split) > 2:
|
|
313
|
+
real_class_name = method_name
|
|
314
|
+
real_method_name = split[2]
|
|
315
|
+
if real_method_name not in _DUNDER_SKIP:
|
|
316
|
+
try:
|
|
317
|
+
with warnings.catch_warnings():
|
|
318
|
+
warnings.simplefilter("ignore")
|
|
319
|
+
real_cls = getattr(sub_mod, real_class_name)
|
|
320
|
+
MethodInterceptor(agent, real_cls).attach(real_method_name, wrapper_func=None, patched_method_name='_custom_method')
|
|
321
|
+
except Exception as e2:
|
|
322
|
+
agent.logger.warning("Could not instrument %s.%s.%s.%s: %s", package_and_module, class_name, real_class_name, real_method_name, e2)
|
|
323
|
+
else:
|
|
324
|
+
agent.logger.warning("Could not instrument METHOD OF CLASS %s, %s, %s : module '%s' has no attribute '%s'", package_and_module, class_name, method_name, package_and_module, class_name)
|
|
325
|
+
except Exception as e:
|
|
326
|
+
agent.logger.warning("Could not instrument METHOD OF CLASS %s, %s, %s : %s", package_and_module, class_name, method_name, e)
|
|
327
|
+
else: # Module Level Method
|
|
328
|
+
method_name = split[0]
|
|
329
|
+
if method_name in _DUNDER_SKIP:
|
|
330
|
+
continue
|
|
331
|
+
try:
|
|
332
|
+
MethodInterceptor(agent, mod).attach(method_name, wrapper_func=None, patched_method_name='_custom_method')
|
|
333
|
+
except Exception as e:
|
|
334
|
+
agent.logger.warning("Could not instrument %s.%s: %s", package_and_module, method_name, e)
|
|
335
|
+
|
|
336
|
+
# After patching module attributes, sync Flask's routing table so that HTTP
|
|
337
|
+
# dispatch also goes through the wrappers (Flask stores direct function-object
|
|
338
|
+
# refs in view_functions, not module attribute lookups).
|
|
339
|
+
_sync_flask_view_functions(mod, agent)
|
|
340
|
+
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import ast
|
|
3
|
+
import json
|
|
4
|
+
import ctypes
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import pyclbr
|
|
8
|
+
import datetime
|
|
9
|
+
from pythonagent.agent.internal.proc_compat import get_process_name
|
|
10
|
+
from pythonagent.agent.probes.Instrumentation import module_version_resolver
|
|
11
|
+
import hashlib
|
|
12
|
+
import inspect
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def newmodulefinder():
|
|
16
|
+
import sys
|
|
17
|
+
from modulefinder import ModuleFinder
|
|
18
|
+
finder = ModuleFinder()
|
|
19
|
+
#finder.run_script(sys.argv[0])
|
|
20
|
+
modulelist = []
|
|
21
|
+
module_dict = {}
|
|
22
|
+
# print('Loaded modules:')
|
|
23
|
+
|
|
24
|
+
#for name, mod in finder.modules.items():
|
|
25
|
+
for name, mod in sys.modules.items():
|
|
26
|
+
#print("name {}".format(name))
|
|
27
|
+
#print("mod {}".format(mod.__dict__))
|
|
28
|
+
|
|
29
|
+
#if mod.__file__ is None:
|
|
30
|
+
if not hasattr(mod, "__file__"):
|
|
31
|
+
pass
|
|
32
|
+
else:
|
|
33
|
+
module_dict[mod.__name__] = mod.__file__
|
|
34
|
+
#modulelist.append(mod.__file__)
|
|
35
|
+
#print(mod.__name__)
|
|
36
|
+
return module_dict
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
import re
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def regex_match(list_of_regex, expression):
|
|
43
|
+
for regex in list_of_regex:
|
|
44
|
+
if re.search(str(regex), expression, re.IGNORECASE):
|
|
45
|
+
return 1
|
|
46
|
+
else:
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def cls_mett_find(node, package_and_module, f, method_filter_list, class_filter_list, jar_field=''):
|
|
51
|
+
outfromad = ""
|
|
52
|
+
num_of_methods = 0
|
|
53
|
+
|
|
54
|
+
functions = []
|
|
55
|
+
classes = []
|
|
56
|
+
|
|
57
|
+
if not method_filter_list and not class_filter_list: # Case 1: No Method Filter and No Class filter
|
|
58
|
+
for n in node.body:
|
|
59
|
+
if isinstance(n, ast.FunctionDef):
|
|
60
|
+
functions.append(n)
|
|
61
|
+
if isinstance(n, ast.ClassDef):
|
|
62
|
+
classes.append(n)
|
|
63
|
+
|
|
64
|
+
elif method_filter_list and not class_filter_list: # Case 2: Only Method Filter
|
|
65
|
+
for n in node.body:
|
|
66
|
+
if isinstance(n, ast.FunctionDef) and regex_match(method_filter_list, n.name):
|
|
67
|
+
functions.append(n)
|
|
68
|
+
if isinstance(n, ast.ClassDef):
|
|
69
|
+
for t in n.body:
|
|
70
|
+
if isinstance(t, ast.FunctionDef) and regex_match(method_filter_list, t.name):
|
|
71
|
+
classes.append(n)
|
|
72
|
+
break # Bug 3 fix: append the class once, not once per matching method
|
|
73
|
+
|
|
74
|
+
elif not method_filter_list and class_filter_list: # Case 3: Only Class Filter
|
|
75
|
+
for n in node.body:
|
|
76
|
+
if isinstance(n, ast.ClassDef) and regex_match(class_filter_list, n.name):
|
|
77
|
+
classes.append(n)
|
|
78
|
+
|
|
79
|
+
elif method_filter_list and class_filter_list: # Case 4: Both Method and Class Filter
|
|
80
|
+
for n in node.body:
|
|
81
|
+
if isinstance(n, ast.FunctionDef) and regex_match(method_filter_list, n.name):
|
|
82
|
+
functions.append(n)
|
|
83
|
+
|
|
84
|
+
if isinstance(n, ast.ClassDef) and regex_match(class_filter_list, n.name):
|
|
85
|
+
for t in n.body:
|
|
86
|
+
if isinstance(t, ast.FunctionDef) and regex_match(method_filter_list, t.name):
|
|
87
|
+
classes.append(n)
|
|
88
|
+
|
|
89
|
+
lif = []
|
|
90
|
+
for function in functions:
|
|
91
|
+
if type(function.name)==str:
|
|
92
|
+
lif.append(function.name)
|
|
93
|
+
num_of_methods = len(lif)
|
|
94
|
+
joined_string = ",".join(lif)
|
|
95
|
+
|
|
96
|
+
if len(joined_string)>0:
|
|
97
|
+
# 4th field after methods is the jar-name-equivalent field (mirrors the NodeJS/Java sibling
|
|
98
|
+
# agents' module|methods|interface|jar layout): "<pkgName>@<pkgVersion>", suffixed "(app)"
|
|
99
|
+
# when the file belongs to the monitored application itself, or blank if unresolved.
|
|
100
|
+
outfromad = package_and_module + "||" + joined_string + "|" + jar_field + "\n"
|
|
101
|
+
"""
|
|
102
|
+
if pkgname:
|
|
103
|
+
#print("if pkgname")
|
|
104
|
+
if module_name == "__init__":
|
|
105
|
+
#print("if module init")
|
|
106
|
+
outfromad = pkgname+"||"+joined_string+"\n"
|
|
107
|
+
else:
|
|
108
|
+
#print("else module init")
|
|
109
|
+
outfromad = pkgname+"."+module_name+"||"+joined_string+"\n"
|
|
110
|
+
else:
|
|
111
|
+
#print("else pkgname")
|
|
112
|
+
outfromad = module_name+"||"+joined_string+"\n"
|
|
113
|
+
"""
|
|
114
|
+
#print("outfromad:", outfromad)
|
|
115
|
+
#f.writelines(outfromad)
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
outfromad = pkgname+"."+module_name+"||"+joined_string+"\n"
|
|
119
|
+
f.writelines(pkgname+"."+module_name+"||"+joined_string+"\n")
|
|
120
|
+
except:
|
|
121
|
+
outfromad = module_name+"||"+joined_string+"\n"
|
|
122
|
+
f.writelines(module_name+"||"+joined_string+"\n")
|
|
123
|
+
"""
|
|
124
|
+
#if module_name == "app":
|
|
125
|
+
# print("outfromad : {}".format(outfromad))
|
|
126
|
+
|
|
127
|
+
for class_ in classes:
|
|
128
|
+
li = []
|
|
129
|
+
all_methods = [n for n in class_.body if isinstance(n, ast.FunctionDef)]
|
|
130
|
+
methods = []
|
|
131
|
+
if method_filter_list:
|
|
132
|
+
for method in all_methods:
|
|
133
|
+
if regex_match(method_filter_list, method.name):
|
|
134
|
+
methods.append(method)
|
|
135
|
+
else:
|
|
136
|
+
methods = all_methods
|
|
137
|
+
|
|
138
|
+
if len(methods)>0:
|
|
139
|
+
for method in methods:
|
|
140
|
+
if type(method.name)==str:
|
|
141
|
+
li.append(method.name)
|
|
142
|
+
num_of_methods += len(li)
|
|
143
|
+
joined_stringf = ",".join(li)
|
|
144
|
+
#if "flask" in pkgname:
|
|
145
|
+
# print("joined_stringf", joined_stringf)
|
|
146
|
+
if len(joined_stringf)>0:
|
|
147
|
+
outfromad += package_and_module + "|" + class_.name + "|" + joined_stringf + "|" + jar_field + "\n"
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
if pkgname:
|
|
151
|
+
#print("if pkgname")
|
|
152
|
+
if module_name == "__init__":
|
|
153
|
+
|
|
154
|
+
#outfromad += "\n"+pkgname+"|"+class_.name+"|"+joined_stringf+"\n"
|
|
155
|
+
outfromad += pkgname+"|"+class_.name+"|"+joined_stringf+"\n"
|
|
156
|
+
|
|
157
|
+
else:
|
|
158
|
+
#print("else module init")
|
|
159
|
+
#outfromad += "\n"+pkgname+"."+module_name+"|"+class_.name+"|"+joined_stringf+"\n"
|
|
160
|
+
outfromad += pkgname+"."+module_name+"|"+class_.name+"|"+joined_stringf+"\n"
|
|
161
|
+
else:
|
|
162
|
+
#print("else pkgname")
|
|
163
|
+
#outfromad += "\n"+module_name+"|"+class_.name+"|"+joined_stringf+"\n"
|
|
164
|
+
outfromad += module_name+"|"+class_.name+"|"+joined_stringf+"\n"
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
outfromad += "\n"+pkgname+"."+module_name+"|"+class_.name+"|"+joined_stringf+"\n"
|
|
170
|
+
f.writelines(pkgname+"."+module_name+"|"+class_.name+"|"+joined_stringf+"\n")
|
|
171
|
+
except:
|
|
172
|
+
outfromad += "\n"+module_name+"|"+class_.name+"|"+joined_stringf+"\n"
|
|
173
|
+
f.writelines(module_name+"|"+class_.name+"|"+joined_stringf+"\n")
|
|
174
|
+
"""
|
|
175
|
+
f.writelines(outfromad)
|
|
176
|
+
return outfromad,num_of_methods
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def find(method_filter_list, class_filter_list):
|
|
180
|
+
complete_output_str = ""
|
|
181
|
+
procname = get_process_name()
|
|
182
|
+
ndhome = os.environ.get("NDHOME")
|
|
183
|
+
#pathforfile = ndhome + "/python/logs/"
|
|
184
|
+
pathforfile = "/tmp/"
|
|
185
|
+
filename = str(procname + "_AD.txt")
|
|
186
|
+
file_to_open = os.path.join(pathforfile, filename)
|
|
187
|
+
f = open(file_to_open, "w")
|
|
188
|
+
f.writelines(
|
|
189
|
+
"************************************ AUTO-DISCOVERY **************************************** \n"
|
|
190
|
+
)
|
|
191
|
+
nylist = newmodulefinder()
|
|
192
|
+
num_of_modules = len(nylist)
|
|
193
|
+
num_of_methods = 0
|
|
194
|
+
|
|
195
|
+
for package_and_module, path in nylist.items():
|
|
196
|
+
# Bug 6: use os.path for cross-platform __main__ name extraction
|
|
197
|
+
if package_and_module == "__main__":
|
|
198
|
+
package_and_module = os.path.splitext(os.path.basename(path))[0]
|
|
199
|
+
|
|
200
|
+
# Bug 2: skip None paths and non-Python source files (C extensions, .pyc, etc.)
|
|
201
|
+
if not path or not path.endswith('.py'):
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
# Bug 1: reset node so a parse failure never bleeds into the next module
|
|
205
|
+
node = None
|
|
206
|
+
try:
|
|
207
|
+
with open(path) as ff:
|
|
208
|
+
node = ast.parse(ff.read())
|
|
209
|
+
except Exception as e:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
if node is None:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
pkg_info = module_version_resolver.resolve(path)
|
|
217
|
+
jar_field = module_version_resolver.format_jar_field(pkg_info)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
jar_field = ''
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
outputfromaf, methodscount = cls_mett_find(node, package_and_module, f, method_filter_list,
|
|
223
|
+
class_filter_list, jar_field)
|
|
224
|
+
except Exception as e:
|
|
225
|
+
print(e)
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
num_of_methods+=methodscount
|
|
229
|
+
complete_output_str+=outputfromaf
|
|
230
|
+
except:
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
#print("TOTAL METHODS ",num_of_methods)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
return complete_output_str, num_of_modules, num_of_methods
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
if __name__ == "__main__":
|
|
241
|
+
find()
|
|
242
|
+
|
|
243
|
+
|