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.
Files changed (180) hide show
  1. cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
  2. cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
  3. cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
  4. cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
  5. cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
  6. pythonagent/__init__.py +22 -0
  7. pythonagent/agent/__init__.py +219 -0
  8. pythonagent/agent/internal/__init__.py +1 -0
  9. pythonagent/agent/internal/agent.py +1651 -0
  10. pythonagent/agent/internal/framesinfo.py +152 -0
  11. pythonagent/agent/internal/heap_dump.py +39 -0
  12. pythonagent/agent/internal/intercept_module.py +70 -0
  13. pythonagent/agent/internal/logs.py +122 -0
  14. pythonagent/agent/internal/metadata/__init__.py +0 -0
  15. pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
  16. pythonagent/agent/internal/proc_compat.py +75 -0
  17. pythonagent/agent/internal/profile.py +47 -0
  18. pythonagent/agent/internal/provider.py +83 -0
  19. pythonagent/agent/internal/thread_dump.py +85 -0
  20. pythonagent/agent/internal/udp.py +245 -0
  21. pythonagent/agent/internal/udp_message.py +800 -0
  22. pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
  23. pythonagent/agent/probes/Instrumentation/find.py +243 -0
  24. pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
  25. pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
  26. pythonagent/agent/probes/Instrumentation/parser.py +129 -0
  27. pythonagent/agent/probes/__init__.py +219 -0
  28. pythonagent/agent/probes/base.py +303 -0
  29. pythonagent/agent/probes/cache/__init__.py +51 -0
  30. pythonagent/agent/probes/cache/redis.py +119 -0
  31. pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
  32. pythonagent/agent/probes/coroutines/__init__.py +1 -0
  33. pythonagent/agent/probes/coroutines/asyncio.py +63 -0
  34. pythonagent/agent/probes/elasticdb/__init__.py +7 -0
  35. pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
  36. pythonagent/agent/probes/frameworks/__init__.py +27 -0
  37. pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
  38. pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
  39. pythonagent/agent/probes/frameworks/aisess.py +151 -0
  40. pythonagent/agent/probes/frameworks/asgi.py +340 -0
  41. pythonagent/agent/probes/frameworks/bottle.py +27 -0
  42. pythonagent/agent/probes/frameworks/cherry.py +25 -0
  43. pythonagent/agent/probes/frameworks/django.py +128 -0
  44. pythonagent/agent/probes/frameworks/falcon.py +21 -0
  45. pythonagent/agent/probes/frameworks/fastapi.py +35 -0
  46. pythonagent/agent/probes/frameworks/flask.py +30 -0
  47. pythonagent/agent/probes/frameworks/pyramid.py +56 -0
  48. pythonagent/agent/probes/frameworks/test.py +108 -0
  49. pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
  50. pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
  51. pythonagent/agent/probes/frameworks/wsgi.py +353 -0
  52. pythonagent/agent/probes/grpc/__init__.py +76 -0
  53. pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
  54. pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
  55. pythonagent/agent/probes/havoc/__init__.py +0 -0
  56. pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
  57. pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
  58. pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
  59. pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
  60. pythonagent/agent/probes/http/__init__.py +49 -0
  61. pythonagent/agent/probes/http/aiohttp_client.py +59 -0
  62. pythonagent/agent/probes/http/boto.py +12 -0
  63. pythonagent/agent/probes/http/httplib.py +110 -0
  64. pythonagent/agent/probes/http/httpx_client.py +116 -0
  65. pythonagent/agent/probes/http/requests.py +15 -0
  66. pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
  67. pythonagent/agent/probes/http/urllib3.py +16 -0
  68. pythonagent/agent/probes/langchain/__init__.py +21 -0
  69. pythonagent/agent/probes/langchain/base_tool.py +95 -0
  70. pythonagent/agent/probes/langchain/langchain_community.py +136 -0
  71. pythonagent/agent/probes/langchain/langchain_core.py +32 -0
  72. pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
  73. pythonagent/agent/probes/logging/__init__.py +106 -0
  74. pythonagent/agent/probes/message_brokers/__init__.py +4 -0
  75. pythonagent/agent/probes/message_brokers/pika.py +126 -0
  76. pythonagent/agent/probes/mongodb/__init__.py +6 -0
  77. pythonagent/agent/probes/mongodb/pymongo.py +286 -0
  78. pythonagent/agent/probes/openai/__init__.py +3 -0
  79. pythonagent/agent/probes/openai/openai.py +797 -0
  80. pythonagent/agent/probes/span.py +101 -0
  81. pythonagent/agent/probes/sql/__init__.py +13 -0
  82. pythonagent/agent/probes/sql/botocores3.py +51 -0
  83. pythonagent/agent/probes/sql/dbapi.py +285 -0
  84. pythonagent/agent/probes/sql/dynamodb.py +90 -0
  85. pythonagent/agent/probes/sql/mysql_connector.py +24 -0
  86. pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
  87. pythonagent/agent/probes/sql/mysqldb.py +43 -0
  88. pythonagent/agent/probes/sql/psycopg2.py +174 -0
  89. pythonagent/agent/probes/sql/pymysql.py +25 -0
  90. pythonagent/bootstrap/__init__.py +0 -0
  91. pythonagent/bootstrap/cav_gunicorn.py +26 -0
  92. pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
  93. pythonagent/bootstrap/run.py +47 -0
  94. pythonagent/bootstrap/sitecustomize.py +287 -0
  95. pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
  96. pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
  97. pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
  98. pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
  99. pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
  100. pythonagent/config.py +279 -0
  101. pythonagent/find.py +72 -0
  102. pythonagent/find_mod_cls_name.py +54 -0
  103. pythonagent/lang.py +131 -0
  104. pythonagent/lib.py +91 -0
  105. pythonagent/main/__init__.py +0 -0
  106. pythonagent/main/pytrace/__init__.py +79 -0
  107. pythonagent/main/pytrace/commands/__init__.py +0 -0
  108. pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
  109. pythonagent/main/pytrace/commands/run.py +401 -0
  110. pythonagent/main/pytrace/pytrace.py +133 -0
  111. pythonagent/main/wsgi.py +6 -0
  112. pythonagent/main.py +27 -0
  113. pythonagent/run.py +46 -0
  114. pythonagent/sqins.py +8 -0
  115. pythonagent/test.py +73 -0
  116. pythonagent/utils.py +168 -0
  117. pythonagent/vendor/__init__.py +0 -0
  118. pythonagent/vendor/pympler/__init__.py +1 -0
  119. pythonagent/vendor/pympler/asizeof.py +2810 -0
  120. pythonagent/vendor/pympler/charts.py +62 -0
  121. pythonagent/vendor/pympler/classtracker.py +590 -0
  122. pythonagent/vendor/pympler/classtracker_stats.py +780 -0
  123. pythonagent/vendor/pympler/garbagegraph.py +80 -0
  124. pythonagent/vendor/pympler/mprofile.py +97 -0
  125. pythonagent/vendor/pympler/muppy.py +275 -0
  126. pythonagent/vendor/pympler/panels.py +115 -0
  127. pythonagent/vendor/pympler/process.py +238 -0
  128. pythonagent/vendor/pympler/py.typed +0 -0
  129. pythonagent/vendor/pympler/refbrowser.py +451 -0
  130. pythonagent/vendor/pympler/refgraph.py +350 -0
  131. pythonagent/vendor/pympler/summary.py +321 -0
  132. pythonagent/vendor/pympler/tracker.py +267 -0
  133. pythonagent/vendor/pympler/util/__init__.py +0 -0
  134. pythonagent/vendor/pympler/util/bottle.py +3809 -0
  135. pythonagent/vendor/pympler/util/compat.py +23 -0
  136. pythonagent/vendor/pympler/util/stringutils.py +77 -0
  137. pythonagent/vendor/pympler/web.py +346 -0
  138. pythonagent/vendor/werkzeug/__init__.py +20 -0
  139. pythonagent/vendor/werkzeug/_compat.py +228 -0
  140. pythonagent/vendor/werkzeug/_internal.py +473 -0
  141. pythonagent/vendor/werkzeug/_reloader.py +341 -0
  142. pythonagent/vendor/werkzeug/datastructures.py +3120 -0
  143. pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
  144. pythonagent/vendor/werkzeug/debug/console.py +218 -0
  145. pythonagent/vendor/werkzeug/debug/repr.py +297 -0
  146. pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
  147. pythonagent/vendor/werkzeug/exceptions.py +829 -0
  148. pythonagent/vendor/werkzeug/filesystem.py +64 -0
  149. pythonagent/vendor/werkzeug/formparser.py +584 -0
  150. pythonagent/vendor/werkzeug/http.py +1307 -0
  151. pythonagent/vendor/werkzeug/local.py +420 -0
  152. pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
  153. pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
  154. pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
  155. pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
  156. pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
  157. pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
  158. pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
  159. pythonagent/vendor/werkzeug/posixemulation.py +117 -0
  160. pythonagent/vendor/werkzeug/routing.py +2210 -0
  161. pythonagent/vendor/werkzeug/security.py +249 -0
  162. pythonagent/vendor/werkzeug/serving.py +1117 -0
  163. pythonagent/vendor/werkzeug/test.py +1123 -0
  164. pythonagent/vendor/werkzeug/testapp.py +241 -0
  165. pythonagent/vendor/werkzeug/urls.py +1138 -0
  166. pythonagent/vendor/werkzeug/useragents.py +202 -0
  167. pythonagent/vendor/werkzeug/utils.py +778 -0
  168. pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
  169. pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
  170. pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
  171. pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
  172. pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
  173. pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
  174. pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
  175. pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
  176. pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
  177. pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
  178. pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
  179. pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
  180. 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
+