whatap-python 2.1.0__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 (227) hide show
  1. whatap/LICENSE +0 -0
  2. whatap/README.rst +49 -0
  3. whatap/__init__.py +923 -0
  4. whatap/__main__.py +4 -0
  5. whatap/agent/darwin/amd64/whatap_python +0 -0
  6. whatap/agent/darwin/arm64/whatap_python +0 -0
  7. whatap/agent/linux/amd64/whatap_python +0 -0
  8. whatap/agent/linux/arm64/whatap_python +0 -0
  9. whatap/agent/windows/whatap_python.exe +0 -0
  10. whatap/bootstrap/__init__.py +0 -0
  11. whatap/bootstrap/sitecustomize.py +19 -0
  12. whatap/build.py +4 -0
  13. whatap/conf/__init__.py +0 -0
  14. whatap/conf/configuration.py +280 -0
  15. whatap/conf/configure.py +105 -0
  16. whatap/conf/license.py +49 -0
  17. whatap/control/__init__.py +0 -0
  18. whatap/counter/__init__.py +14 -0
  19. whatap/counter/counter_manager.py +45 -0
  20. whatap/counter/tasks/__init__.py +3 -0
  21. whatap/counter/tasks/base_task.py +26 -0
  22. whatap/counter/tasks/llm_evaluator_task.py +501 -0
  23. whatap/counter/tasks/llm_log_sink_task.py +309 -0
  24. whatap/counter/tasks/llm_stat_task.py +78 -0
  25. whatap/counter/tasks/openfiledescriptor.py +67 -0
  26. whatap/io/__init__.py +1 -0
  27. whatap/io/data_inputx.py +161 -0
  28. whatap/io/data_outputx.py +262 -0
  29. whatap/llm/__init__.py +17 -0
  30. whatap/llm/definitions.py +43 -0
  31. whatap/llm/evaluators/__init__.py +136 -0
  32. whatap/llm/evaluators/base.py +114 -0
  33. whatap/llm/evaluators/builtins/__init__.py +91 -0
  34. whatap/llm/evaluators/builtins/answer_relevance.py +46 -0
  35. whatap/llm/evaluators/builtins/combined_judge.py +271 -0
  36. whatap/llm/evaluators/builtins/factuality.py +71 -0
  37. whatap/llm/evaluators/builtins/hallucination.py +97 -0
  38. whatap/llm/evaluators/builtins/llm_judge.py +516 -0
  39. whatap/llm/evaluators/builtins/pii_leak.py +214 -0
  40. whatap/llm/evaluators/builtins/prompt_injection.py +71 -0
  41. whatap/llm/evaluators/builtins/toxicity.py +53 -0
  42. whatap/llm/evaluators/builtins/url_scan.py +194 -0
  43. whatap/llm/evaluators/registry.py +192 -0
  44. whatap/llm/evaluators/sampler.py +83 -0
  45. whatap/llm/evaluators/scope.py +334 -0
  46. whatap/llm/features.py +66 -0
  47. whatap/llm/log_sink_packs/__init__.py +9 -0
  48. whatap/llm/log_sink_packs/llm_input_message.py +16 -0
  49. whatap/llm/log_sink_packs/llm_log_sink_pack.py +72 -0
  50. whatap/llm/log_sink_packs/llm_output_message.py +19 -0
  51. whatap/llm/log_sink_packs/llm_step_eval_status.py +94 -0
  52. whatap/llm/log_sink_packs/llm_step_status.py +118 -0
  53. whatap/llm/log_sink_packs/llm_system_message.py +16 -0
  54. whatap/llm/log_sink_packs/llm_tool_calls.py +44 -0
  55. whatap/llm/log_sink_packs/llm_tool_results.py +16 -0
  56. whatap/llm/log_sink_packs/llm_tx_status.py +108 -0
  57. whatap/llm/pricing.py +236 -0
  58. whatap/llm/prompt_meta.py +288 -0
  59. whatap/llm/providers/__init__.py +0 -0
  60. whatap/llm/providers/anthropic/__init__.py +37 -0
  61. whatap/llm/providers/anthropic/messages/__init__.py +0 -0
  62. whatap/llm/providers/anthropic/messages/messages.py +70 -0
  63. whatap/llm/providers/anthropic/messages/messages_context.py +76 -0
  64. whatap/llm/providers/anthropic/messages/messages_extractor.py +126 -0
  65. whatap/llm/providers/interceptor.py +182 -0
  66. whatap/llm/providers/openai/__init__.py +133 -0
  67. whatap/llm/providers/openai/chat/__init__.py +0 -0
  68. whatap/llm/providers/openai/chat/chat.py +82 -0
  69. whatap/llm/providers/openai/chat/chat_context.py +78 -0
  70. whatap/llm/providers/openai/chat/chat_extractor.py +127 -0
  71. whatap/llm/providers/openai/completions/__init__.py +0 -0
  72. whatap/llm/providers/openai/completions/completions.py +70 -0
  73. whatap/llm/providers/openai/completions/completions_context.py +31 -0
  74. whatap/llm/providers/openai/completions/completions_extractor.py +61 -0
  75. whatap/llm/providers/openai/content_parser.py +41 -0
  76. whatap/llm/providers/openai/embeddings/__init__.py +0 -0
  77. whatap/llm/providers/openai/embeddings/embeddings.py +59 -0
  78. whatap/llm/providers/openai/embeddings/embeddings_context.py +25 -0
  79. whatap/llm/providers/openai/embeddings/embeddings_extractor.py +26 -0
  80. whatap/llm/providers/openai/responses/__init__.py +0 -0
  81. whatap/llm/providers/openai/responses/responses.py +70 -0
  82. whatap/llm/providers/openai/responses/responses_context.py +88 -0
  83. whatap/llm/providers/openai/responses/responses_extractor.py +126 -0
  84. whatap/llm/providers/stream_accumulator.py +73 -0
  85. whatap/llm/stats/__init__.py +35 -0
  86. whatap/llm/stats/active_stat.py +86 -0
  87. whatap/llm/stats/answer_relevance_eval_stat.py +10 -0
  88. whatap/llm/stats/api_status_stat.py +35 -0
  89. whatap/llm/stats/base_stat.py +107 -0
  90. whatap/llm/stats/combined_judge_eval_stat.py +11 -0
  91. whatap/llm/stats/error_stat.py +59 -0
  92. whatap/llm/stats/eval_stat.py +225 -0
  93. whatap/llm/stats/factuality_eval_stat.py +10 -0
  94. whatap/llm/stats/feature_stat.py +104 -0
  95. whatap/llm/stats/finish_stat.py +105 -0
  96. whatap/llm/stats/hallucination_eval_stat.py +10 -0
  97. whatap/llm/stats/meter.py +18 -0
  98. whatap/llm/stats/perf_stat.py +117 -0
  99. whatap/llm/stats/pii_leak_eval_stat.py +12 -0
  100. whatap/llm/stats/prompt_injection_eval_stat.py +10 -0
  101. whatap/llm/stats/token_usage_stat.py +133 -0
  102. whatap/llm/stats/toxicity_eval_stat.py +10 -0
  103. whatap/llm/stats/url_scan_eval_stat.py +12 -0
  104. whatap/net/__init__.py +0 -0
  105. whatap/net/async_sender.py +107 -0
  106. whatap/net/packet_enum.py +44 -0
  107. whatap/net/packet_type_enum.py +31 -0
  108. whatap/net/param_def.py +69 -0
  109. whatap/net/stackhelper.py +87 -0
  110. whatap/net/udp_session.py +394 -0
  111. whatap/net/udp_thread.py +54 -0
  112. whatap/pack/__init__.py +0 -0
  113. whatap/pack/logSinkPack.py +77 -0
  114. whatap/pack/pack.py +34 -0
  115. whatap/pack/pack_enum.py +41 -0
  116. whatap/pack/tagCountPack.py +61 -0
  117. whatap/scripts/__init__.py +208 -0
  118. whatap/trace/__init__.py +12 -0
  119. whatap/trace/mod/__init__.py +0 -0
  120. whatap/trace/mod/amqp/__init__.py +0 -0
  121. whatap/trace/mod/amqp/kombu.py +122 -0
  122. whatap/trace/mod/amqp/pika.py +62 -0
  123. whatap/trace/mod/application/__init__.py +0 -0
  124. whatap/trace/mod/application/bottle.py +34 -0
  125. whatap/trace/mod/application/celery.py +81 -0
  126. whatap/trace/mod/application/cherrypy.py +30 -0
  127. whatap/trace/mod/application/django.py +287 -0
  128. whatap/trace/mod/application/django_asgi.py +266 -0
  129. whatap/trace/mod/application/django_py3.py +251 -0
  130. whatap/trace/mod/application/fastapi/__init__.py +31 -0
  131. whatap/trace/mod/application/fastapi/endpoint.py +73 -0
  132. whatap/trace/mod/application/fastapi/exception_log.py +63 -0
  133. whatap/trace/mod/application/fastapi/instrumentation.py +204 -0
  134. whatap/trace/mod/application/fastapi/scope.py +115 -0
  135. whatap/trace/mod/application/fastapi/transaction.py +67 -0
  136. whatap/trace/mod/application/flask.py +52 -0
  137. whatap/trace/mod/application/frappe.py +224 -0
  138. whatap/trace/mod/application/graphql.py +170 -0
  139. whatap/trace/mod/application/nameko.py +39 -0
  140. whatap/trace/mod/application/odoo.py +63 -0
  141. whatap/trace/mod/application/starlette.py +126 -0
  142. whatap/trace/mod/application/tornado.py +163 -0
  143. whatap/trace/mod/application/wsgi.py +195 -0
  144. whatap/trace/mod/database/__init__.py +0 -0
  145. whatap/trace/mod/database/cxoracle.py +49 -0
  146. whatap/trace/mod/database/mongo.py +169 -0
  147. whatap/trace/mod/database/mysql.py +80 -0
  148. whatap/trace/mod/database/neo4j.py +90 -0
  149. whatap/trace/mod/database/psycopg2.py +45 -0
  150. whatap/trace/mod/database/psycopg3.py +359 -0
  151. whatap/trace/mod/database/redis.py +122 -0
  152. whatap/trace/mod/database/sqlalchemy.py +213 -0
  153. whatap/trace/mod/database/sqlite3.py +130 -0
  154. whatap/trace/mod/database/util.py +630 -0
  155. whatap/trace/mod/email/__init__.py +0 -0
  156. whatap/trace/mod/email/smtp.py +78 -0
  157. whatap/trace/mod/httpc/__init__.py +0 -0
  158. whatap/trace/mod/httpc/django.py +31 -0
  159. whatap/trace/mod/httpc/httplib.py +70 -0
  160. whatap/trace/mod/httpc/httpx.py +62 -0
  161. whatap/trace/mod/httpc/requests.py +20 -0
  162. whatap/trace/mod/httpc/urllib3.py +27 -0
  163. whatap/trace/mod/httpc/util.py +388 -0
  164. whatap/trace/mod/logging.py +161 -0
  165. whatap/trace/mod/plugin.py +84 -0
  166. whatap/trace/mod/standalone/__init__.py +0 -0
  167. whatap/trace/mod/standalone/multiple.py +293 -0
  168. whatap/trace/mod/standalone/single.py +135 -0
  169. whatap/trace/simple_trace_context.py +18 -0
  170. whatap/trace/trace_context.py +212 -0
  171. whatap/trace/trace_context_manager.py +244 -0
  172. whatap/trace/trace_error.py +84 -0
  173. whatap/trace/trace_handler.py +89 -0
  174. whatap/trace/trace_import.py +91 -0
  175. whatap/trace/trace_module_definition.py +156 -0
  176. whatap/util/__init__.py +0 -0
  177. whatap/util/bit_util.py +49 -0
  178. whatap/util/cardinality/__init__.py +0 -0
  179. whatap/util/cardinality/hyperloglog.py +84 -0
  180. whatap/util/cardinality/murmurhash.py +20 -0
  181. whatap/util/cardinality/registerset.py +60 -0
  182. whatap/util/compare_util.py +19 -0
  183. whatap/util/date_util.py +55 -0
  184. whatap/util/debug_util.py +73 -0
  185. whatap/util/escape_literal_sql.py +233 -0
  186. whatap/util/frame_util.py +20 -0
  187. whatap/util/hash_util.py +103 -0
  188. whatap/util/hexa32.py +66 -0
  189. whatap/util/int_set.py +199 -0
  190. whatap/util/ip_util.py +63 -0
  191. whatap/util/keygen.py +11 -0
  192. whatap/util/linked_list.py +113 -0
  193. whatap/util/linked_map.py +359 -0
  194. whatap/util/metering_util.py +103 -0
  195. whatap/util/request_double_queue.py +68 -0
  196. whatap/util/request_queue.py +60 -0
  197. whatap/util/string_util.py +20 -0
  198. whatap/util/throttle_util.py +99 -0
  199. whatap/util/userid_util.py +134 -0
  200. whatap/value/__init__.py +1 -0
  201. whatap/value/blob_value.py +38 -0
  202. whatap/value/boolean_value.py +33 -0
  203. whatap/value/decimal_value.py +36 -0
  204. whatap/value/double_summary.py +86 -0
  205. whatap/value/double_value.py +33 -0
  206. whatap/value/float_array.py +42 -0
  207. whatap/value/float_value.py +34 -0
  208. whatap/value/int_array.py +42 -0
  209. whatap/value/ip4_value.py +50 -0
  210. whatap/value/list_value.py +105 -0
  211. whatap/value/long_array.py +44 -0
  212. whatap/value/long_summary.py +83 -0
  213. whatap/value/map_value.py +154 -0
  214. whatap/value/null_value.py +21 -0
  215. whatap/value/number_value.py +33 -0
  216. whatap/value/summary_value.py +39 -0
  217. whatap/value/text_array.py +58 -0
  218. whatap/value/text_hash_value.py +37 -0
  219. whatap/value/text_value.py +43 -0
  220. whatap/value/value.py +26 -0
  221. whatap/value/value_enum.py +80 -0
  222. whatap/whatap.conf +14 -0
  223. whatap_python-2.1.0.dist-info/METADATA +87 -0
  224. whatap_python-2.1.0.dist-info/RECORD +227 -0
  225. whatap_python-2.1.0.dist-info/WHEEL +5 -0
  226. whatap_python-2.1.0.dist-info/entry_points.txt +6 -0
  227. whatap_python-2.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,359 @@
1
+ import inspect
2
+
3
+ from whatap.trace.trace_handler import trace_handler, async_trace_handler
4
+ from whatap.trace.mod.database.util import (
5
+ interceptor_db_con, interceptor_db_execute, interceptor_db_close,
6
+ async_interceptor_db_con, async_interceptor_db_execute, async_interceptor_db_close,
7
+ interceptor_pool_get, interceptor_pool_release,
8
+ async_interceptor_pool_get, async_interceptor_pool_release
9
+ )
10
+
11
+ db_info = {}
12
+
13
+
14
+ class BaseCursor:
15
+ def __init__(self, cursor, db_info):
16
+ self._cursor = cursor
17
+ self._db_info = db_info
18
+ self._is_wrapped = True
19
+
20
+ def __getattr__(self, name):
21
+ return getattr(self._cursor, name)
22
+
23
+ def __setattr__(self, name, value):
24
+ if name.startswith('_'):
25
+ object.__setattr__(self, name, value)
26
+ else:
27
+ setattr(self._cursor, name, value)
28
+
29
+ @property
30
+ def connection(self):
31
+ return self._cursor.connection
32
+
33
+ def _execute_wrapper(self, original_execute_method):
34
+ owner = getattr(original_execute_method, "__self__", None)
35
+
36
+ def safe_execute(*args, **kwargs):
37
+ if args and owner is not None and args[0] is owner:
38
+ args = args[1:]
39
+ return original_execute_method(*args, **kwargs)
40
+
41
+ return safe_execute
42
+
43
+
44
+ class SyncCursor(BaseCursor):
45
+ def __enter__(self):
46
+ self._cursor.__enter__()
47
+ return self
48
+
49
+ def __exit__(self, exc_type, exc_val, exc_tb):
50
+ return self._cursor.__exit__(exc_type, exc_val, exc_tb)
51
+
52
+ def execute(self, *args, **kwargs):
53
+ if hasattr(self._cursor, '_is_wrapped'):
54
+ return self._cursor.execute(*args, **kwargs)
55
+ real_execute = self._cursor.execute
56
+ safe_fn = self._execute_wrapper(real_execute)
57
+ return interceptor_db_execute(safe_fn, self._db_info, self._cursor, *args, **kwargs)
58
+
59
+ def executemany(self, *args, **kwargs):
60
+ if hasattr(self._cursor, '_is_wrapped'):
61
+ return self._cursor.executemany(*args, **kwargs)
62
+ real_executemany = getattr(self._cursor, "executemany", None)
63
+ if real_executemany is None:
64
+ return self._cursor.executemany(*args, **kwargs)
65
+ safe_fn = self._execute_wrapper(real_executemany)
66
+ return interceptor_db_execute(safe_fn, self._db_info, self._cursor, *args, **kwargs)
67
+
68
+
69
+ class AsyncCursor(BaseCursor):
70
+ async def __aenter__(self):
71
+ await self._cursor.__aenter__()
72
+ return self
73
+
74
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
75
+ return await self._cursor.__aexit__(exc_type, exc_val, exc_tb)
76
+
77
+ def _async_execute_wrapper(self, original_execute_method):
78
+ owner = getattr(original_execute_method, "__self__", None)
79
+
80
+ async def async_safe_execute(*args, **kwargs):
81
+ if args and owner is not None and args[0] is owner:
82
+ args = args[1:]
83
+ return await original_execute_method(*args, **kwargs)
84
+
85
+ return async_safe_execute
86
+
87
+ async def execute(self, *args, **kwargs):
88
+ if hasattr(self._cursor, '_is_wrapped'):
89
+ return await self._cursor.execute(*args, **kwargs)
90
+ real_execute = self._cursor.execute
91
+ safe_fn = self._async_execute_wrapper(real_execute)
92
+ return await async_interceptor_db_execute(safe_fn, self._db_info, self._cursor, *args, **kwargs)
93
+
94
+ async def executemany(self, *args, **kwargs):
95
+ if hasattr(self._cursor, '_is_wrapped'):
96
+ return await self._cursor.executemany(*args, **kwargs)
97
+ real_executemany = getattr(self._cursor, "executemany", None)
98
+ if real_executemany is None:
99
+ return await self._cursor.executemany(*args, **kwargs)
100
+ safe_fn = self._async_execute_wrapper(real_executemany)
101
+ return await async_interceptor_db_execute(safe_fn, self._db_info, self._cursor, *args, **kwargs)
102
+
103
+
104
+ class BaseConnection:
105
+ def __init__(self, connection, db_info):
106
+ self._connection = connection
107
+ self._db_info = db_info
108
+ self._is_wrapped = True
109
+
110
+ def __getattr__(self, name):
111
+ return getattr(self._connection, name)
112
+
113
+ def __setattr__(self, name, value):
114
+ if name.startswith('_'):
115
+ object.__setattr__(self, name, value)
116
+ else:
117
+ setattr(self._connection, name, value)
118
+
119
+ def _execute_wrapper(self, original_execute_method):
120
+ owner = getattr(original_execute_method, "__self__", None)
121
+
122
+ def safe_execute(*args, **kwargs):
123
+ if args and owner is not None and args[0] is owner:
124
+ args = args[1:]
125
+ return original_execute_method(*args, **kwargs)
126
+
127
+ return safe_execute
128
+
129
+
130
+ class SyncConnection(BaseConnection):
131
+ def __enter__(self):
132
+ self._connection.__enter__()
133
+ return self
134
+
135
+ def __exit__(self, exc_type, exc_val, exc_tb):
136
+ return self._connection.__exit__(exc_type, exc_val, exc_tb)
137
+
138
+ def close(self, *args, **kwargs):
139
+ if self._db_info.get("pool"):
140
+ interceptor_pool_release()
141
+ return self._connection.close(*args, **kwargs)
142
+
143
+ real_close = self._connection.close
144
+ return interceptor_db_close(real_close, *args, **kwargs)
145
+
146
+ def cursor(self, *args, **kwargs):
147
+ real_cursor = self._connection.cursor(*args, **kwargs)
148
+ if hasattr(real_cursor, '_is_wrapped'):
149
+ return real_cursor
150
+ return SyncCursor(real_cursor, self._db_info)
151
+
152
+ def execute(self, *args, **kwargs):
153
+ if hasattr(self._connection, '_is_wrapped'):
154
+ return self._connection.execute(*args, **kwargs)
155
+ real_execute = getattr(self._connection, "execute", None)
156
+ if real_execute is None:
157
+ return self._connection.execute(*args, **kwargs)
158
+ safe_fn = self._execute_wrapper(real_execute)
159
+ return interceptor_db_execute(safe_fn, self._db_info, self._connection, *args, **kwargs)
160
+
161
+
162
+ class AsyncConnection(BaseConnection):
163
+
164
+ def __init__(self, connection, db_info, proxy=None):
165
+ super().__init__(connection, db_info) # 부모 생성자 호출
166
+ self._pool_proxy = proxy # proxy 참조 저장
167
+
168
+ async def __aenter__(self):
169
+ await self._connection.__aenter__()
170
+ return self
171
+
172
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
173
+ if self._db_info.get("pool") and self._pool_proxy:
174
+
175
+ # Pool connection이고 proxy가 있으면 proxy의 __aexit__ 호출
176
+ await async_interceptor_pool_release()
177
+ return await self._pool_proxy.__aexit__(exc_type, exc_val, exc_tb)
178
+
179
+ return await self._connection.__aexit__(exc_type, exc_val, exc_tb)
180
+
181
+ def cursor(self, *args, **kwargs):
182
+ real_cursor = self._connection.cursor(*args, **kwargs)
183
+ if hasattr(real_cursor, '_is_wrapped'):
184
+ return real_cursor
185
+ return AsyncCursor(real_cursor, self._db_info)
186
+
187
+ def _async_execute_wrapper(self, original_execute_method):
188
+ owner = getattr(original_execute_method, "__self__", None)
189
+
190
+ async def async_safe_execute(*args, **kwargs):
191
+ if args and owner is not None and args[0] is owner:
192
+ args = args[1:]
193
+ return await original_execute_method(*args, **kwargs)
194
+
195
+ return async_safe_execute
196
+
197
+ async def execute(self, *args, **kwargs):
198
+ if hasattr(self._connection, '_is_wrapped'):
199
+ return await self._connection.execute(*args, **kwargs)
200
+ real_execute = getattr(self._connection, "execute", None)
201
+ if real_execute is None:
202
+ return await self._connection.execute(*args, **kwargs)
203
+ safe_fn = self._async_execute_wrapper(real_execute)
204
+ return await async_interceptor_db_execute(safe_fn, self._db_info, self._connection, *args, **kwargs)
205
+
206
+ async def close(self, *args, **kwargs):
207
+ if self._db_info.get("pool"):
208
+ await async_interceptor_pool_release()
209
+ return await self._connection.close(*args, **kwargs)
210
+
211
+ real_close = self._connection.close
212
+ return await async_interceptor_db_close(real_close, *args, **kwargs)
213
+
214
+
215
+ def _is_called_by_pool():
216
+ try:
217
+ # 실제로 psycopg_pool은 보통 3-4 레벨 내에서 호출되므로 깊이 5로 결정.
218
+ filenames = [f.filename for f in inspect.stack(context=0)[2:7]]
219
+ return any('psycopg_pool' in f for f in filenames if f)
220
+ except Exception:
221
+ return False
222
+
223
+
224
+ def _sync_wrapper(fn):
225
+ @trace_handler(fn)
226
+ def wrapper(*args, **kwargs):
227
+ if _is_called_by_pool():
228
+ return fn(*args, **kwargs)
229
+
230
+ db_info = {"type": "postgresql"}
231
+
232
+ if args:
233
+ conn_str = args[0]
234
+ if isinstance(args[0], str) and '=' in args[0]:
235
+ parsed_kwargs = dict(
236
+ x.split('=') for x in conn_str.split()
237
+ )
238
+ kwargs.update(parsed_kwargs)
239
+ db_info.update(kwargs)
240
+ else:
241
+ db_info.update({"db_con_stc": "completed"})
242
+ db_info.update({"db_str": args[0]})
243
+
244
+ connection = interceptor_db_con(fn, db_info, *args, **kwargs)
245
+ if hasattr(connection, '_is_wrapped'):
246
+ return connection
247
+ return SyncConnection(connection, dict(db_info))
248
+
249
+ return wrapper
250
+
251
+
252
+ def _async_wrapper(fn):
253
+ @async_trace_handler(fn)
254
+ async def wrapper(*args, **kwargs):
255
+ if _is_called_by_pool():
256
+ return await fn(*args, **kwargs)
257
+
258
+ db_info = {"type": "postgresql"}
259
+
260
+ if args:
261
+ conn_str = args[0]
262
+ if isinstance(args[0], str) and '=' in args[0]:
263
+ parsed_kwargs = dict(
264
+ x.split('=') for x in conn_str.split()
265
+ )
266
+ kwargs.update(parsed_kwargs)
267
+ db_info.update(kwargs)
268
+ else:
269
+ db_info.update({"db_con_stc": "completed"})
270
+ db_info.update({"db_str": args[0]})
271
+
272
+
273
+ connection = await async_interceptor_db_con(fn, db_info, *args, **kwargs)
274
+ if hasattr(connection, '_is_wrapped'):
275
+ return connection
276
+ return AsyncConnection(connection, dict(db_info))
277
+
278
+ return wrapper
279
+
280
+
281
+ def _get_conn_info(connection):
282
+ conn_info = {}
283
+ try:
284
+ if hasattr(connection, 'info'):
285
+ conn_info['host'] = connection.info.host
286
+ conn_info['port'] = connection.info.port
287
+ conn_info['dbname'] = connection.info.dbname
288
+ conn_info['user'] = connection.info.user
289
+ elif hasattr(connection, 'dsn'):
290
+ import re
291
+ dsn_params = dict(x.split('=') for x in re.sub(r'\s*=\s*', '=', connection.dsn).split())
292
+ conn_info.update(dsn_params)
293
+ except Exception as e:
294
+ print(f"Failed to extract connection info: {e}")
295
+ return conn_info
296
+
297
+
298
+ def _pool_getconn_wrapper(original_getconn):
299
+ def wrapper(self, *args, **kwargs):
300
+ connection = original_getconn(self, *args, **kwargs)
301
+ conn_info = _get_conn_info(connection)
302
+ db_info = {"type": "postgresql", "pool": True, **conn_info}
303
+
304
+ interceptor_pool_get(db_info)
305
+
306
+ return SyncConnection(connection, db_info)
307
+
308
+ return wrapper
309
+
310
+
311
+ class AsyncConnectionProxy:
312
+ """AsyncConnectionPool.connection()이 반환하는 proxy 객체를 래핑"""
313
+
314
+ def __init__(self, proxy):
315
+ self._proxy = proxy
316
+ self._wrapped_connection = None
317
+
318
+ async def __aenter__(self):
319
+ connection = await self._proxy.__aenter__()
320
+ conn_info = _get_conn_info(connection)
321
+ db_info = {"type": "postgresql", "pool": True, **conn_info}
322
+ await async_interceptor_pool_get(db_info)
323
+
324
+ self._wrapped_connection = AsyncConnection(connection, db_info, proxy=self._proxy)
325
+ return self._wrapped_connection
326
+
327
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
328
+ if self._wrapped_connection:
329
+ await async_interceptor_pool_release()
330
+ return await self._proxy.__aexit__(exc_type, exc_val, exc_tb)
331
+
332
+
333
+ def _async_pool_connection_wrapper(original_connection):
334
+ """AsyncConnectionPool.connection() 메서드를 래핑"""
335
+
336
+ def wrapper(self, *args, **kwargs):
337
+ proxy = original_connection(self, *args, **kwargs)
338
+ return AsyncConnectionProxy(proxy)
339
+
340
+ return wrapper
341
+
342
+
343
+ def instrument_psycopg(module):
344
+ original_connect = module.connect
345
+ module.connect = _sync_wrapper(original_connect)
346
+ if hasattr(module, 'AsyncConnection') and hasattr(module.AsyncConnection, 'connect'):
347
+ original_async_connect = module.AsyncConnection.connect
348
+ module.AsyncConnection.connect = _async_wrapper(original_async_connect)
349
+
350
+
351
+ def instrument_psycopg_pool(pool_module):
352
+ if hasattr(pool_module, 'ConnectionPool'):
353
+ if hasattr(pool_module.ConnectionPool, 'getconn'):
354
+ pool_module.ConnectionPool.getconn = _pool_getconn_wrapper(pool_module.ConnectionPool.getconn)
355
+
356
+ if hasattr(pool_module, 'AsyncConnectionPool'):
357
+ if hasattr(pool_module.AsyncConnectionPool, 'connection'):
358
+ original_connection = pool_module.AsyncConnectionPool.connection
359
+ pool_module.AsyncConnectionPool.connection = _async_pool_connection_wrapper(original_connection)
@@ -0,0 +1,122 @@
1
+ from whatap.trace import get_dict
2
+ from whatap.trace.trace_handler import trace_handler
3
+ from whatap.trace.trace_error import interceptor_step_error
4
+ from whatap.trace.trace_context_manager import TraceContextManager
5
+ import whatap.net.async_sender as async_sender
6
+ from whatap.net.packet_type_enum import PacketTypeEnum
7
+ from whatap.util.date_util import DateUtil
8
+ import numbers
9
+
10
+ _current_command = None
11
+
12
+
13
+ def intercept_send_command(fn, instance, *args, **kwargs):
14
+ global _current_command
15
+ command = str(args[0]).upper() if args else None
16
+ _current_command = command
17
+
18
+ if command not in {'SET', 'GET', 'DEL', 'HSET', 'HGET'}:
19
+ return fn(instance, *args, **kwargs)
20
+
21
+ ctx = TraceContextManager.getLocalContext()
22
+ if not ctx:
23
+ return fn(instance, *args, **kwargs)
24
+
25
+ start_time = DateUtil.nowSystem()
26
+ ctx.start_time = start_time
27
+
28
+ port_or_path = getattr(instance, 'port', getattr(instance, 'path', None))
29
+ text = 'redis://'
30
+ text += getattr(instance, 'host', 'localhost')
31
+ text += ':{}'.format(port_or_path)
32
+ text += '/'
33
+ text += str(getattr(instance, 'db', 0))
34
+ ctx.active_dbc = text
35
+ ctx.lctx['dbc'] = text
36
+
37
+ ctx.active_dbc = 0
38
+ ctx.db_opening = True
39
+ datas = [text]
40
+ ctx.elapsed = DateUtil.nowSystem() - start_time
41
+ async_sender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, datas)
42
+ query = f"{command} " + " ".join([str(arg)[:20] for arg in args[1:]])
43
+
44
+ start_time = DateUtil.nowSystem()
45
+ ctx.start_time = start_time
46
+ ctx.active_sqlhash = query
47
+
48
+ try:
49
+ callback = fn(instance, *args, **kwargs)
50
+ return callback
51
+ except Exception as e:
52
+ interceptor_step_error(e)
53
+ finally:
54
+ ctx.db_opening = False
55
+ datas = [ctx.lctx.get('dbc', ''), query, 0]
56
+ ctx.elapsed = DateUtil.nowSystem() - start_time
57
+ async_sender.send_packet(PacketTypeEnum.TX_SQL, ctx, datas)
58
+
59
+
60
+ def interceptor_read_response(fn, *args, **kwargs):
61
+ global _current_command
62
+
63
+ if _current_command not in {'SET', 'GET', 'DEL', 'HSET', 'HGET'}:
64
+ _current_command = None
65
+ return fn(*args, **kwargs)
66
+
67
+ ctx = TraceContextManager.getLocalContext()
68
+ if not ctx:
69
+ _current_command = None
70
+ return fn(*args, **kwargs)
71
+
72
+ start_time = DateUtil.nowSystem()
73
+ ctx.start_time = start_time
74
+
75
+ try:
76
+ callback = fn(*args, **kwargs)
77
+
78
+ if callback:
79
+ count = -1
80
+ if isinstance(callback, int):
81
+ count = callback
82
+ elif isinstance(callback, list):
83
+ count = len(callback)
84
+
85
+ if count > -1:
86
+ msg = '{0}: {1}'.format('Fetch bytes', count)
87
+ datas = [msg, ' ', ' ']
88
+ ctx.elapsed = 0
89
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
90
+
91
+ ctx.active_sqlhash = 0
92
+ return callback
93
+ finally:
94
+ _current_command = None
95
+
96
+
97
+ def instrument_redis_connection(module):
98
+ def wrapper(fn):
99
+ @trace_handler(fn)
100
+ def trace(*args, **kwargs):
101
+ callback = intercept_send_command(fn, args[0], *args[1:], **kwargs)
102
+ return callback
103
+
104
+ return trace
105
+
106
+ if hasattr(module, 'Connection') and hasattr(module.Connection, 'send_command'):
107
+ if not getattr(module.Connection.send_command, '_wrapped', False):
108
+ module.Connection.send_command = wrapper(module.Connection.send_command)
109
+ module.Connection.send_command._wrapped = True
110
+
111
+ def wrapper(fn):
112
+ @trace_handler(fn)
113
+ def trace(*args, **kwargs):
114
+ callback = interceptor_read_response(fn, *args, **kwargs)
115
+ return callback
116
+
117
+ return trace
118
+
119
+ if hasattr(module, 'Connection') and hasattr(module.Connection, 'read_response'):
120
+ if not getattr(module.Connection.read_response, '_wrapped', False):
121
+ module.Connection.read_response = wrapper(module.Connection.read_response)
122
+ module.Connection.read_response._wrapped = True
@@ -0,0 +1,213 @@
1
+ from whatap.net.packet_type_enum import PacketTypeEnum
2
+ import whatap.net.async_sender as async_sender
3
+ from whatap.trace.trace_handler import trace_handler
4
+ from whatap.trace.trace_error import interceptor_step_error, sendDebugProfile
5
+ from whatap.util.date_util import DateUtil
6
+ from whatap.trace.trace_context_manager import TraceContextManager
7
+ import sys
8
+ from functools import wraps
9
+
10
+ def instrument_sqlalchemy(module):
11
+ def wrapper(fn):
12
+ @trace_handler(fn)
13
+ def trace(*args, **kwargs):
14
+ ctx = TraceContextManager.getLocalContext()
15
+
16
+ start_time = DateUtil.nowSystem()
17
+ ctx.start_time = start_time
18
+
19
+ text = args[0].bind.url.__to_string__().replace('***', '')
20
+ ctx.lctx['dbc'] = text
21
+
22
+ datas = [' ', ' ', 'DB SESSION INFO: ' + text]
23
+ ctx.elapsed = DateUtil.nowSystem() - start_time
24
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
25
+
26
+ callback = fn(*args, **kwargs)
27
+ return callback
28
+
29
+ return trace
30
+ if sys.modules.get("flask_sqlalchemy") is None:
31
+ module.Session.get_bind = wrapper(module.Session.get_bind)
32
+
33
+ def addQuote(arg_dict):
34
+ quoted_dict = dict()
35
+
36
+ for k, v in arg_dict.items():
37
+ if isinstance(v, str):
38
+ quoted_dict[k] = "'" + v.replace("'", "\\'") + "'"
39
+ else:
40
+ quoted_dict[k] = v
41
+
42
+ return quoted_dict
43
+
44
+ def instrument_sqlalchemy_engine(module):
45
+ def wrapper(fn):
46
+ @trace_handler(fn)
47
+ def trace(*args, **kwargs):
48
+ ctx = TraceContextManager.getLocalContext()
49
+ cursor = args[1]
50
+ query = None
51
+ if len(args) > 3 and args[3]:
52
+ try:
53
+ ##oracle을 사용하는 경우
54
+ if (type(args[3]) == dict) and (":" in args[2]) and ("oracle" in str(args[0])):
55
+ oracle_sql_query = args[2]
56
+ for k, v in args[3].items():
57
+ replaced_key = f":{k}"
58
+ replaced_value = f"'{v}'"
59
+ oracle_sql_query = oracle_sql_query.replace(replaced_key,
60
+ replaced_value) if replaced_key in oracle_sql_query else None
61
+ query = oracle_sql_query
62
+
63
+ ##mysql를 사용하는 경우
64
+ elif (type(args[3]) == tuple) and ("%s" in args[2]) and ("mysql" in str(args[0])):
65
+ my_sql_query = args[2]
66
+ for v in args[3]:
67
+ replaced_value = f"'{v}'"
68
+ my_sql_query = my_sql_query.replace("%s", replaced_value,
69
+ 1) if "%s" in my_sql_query else None
70
+ query = my_sql_query
71
+ else:
72
+ query = args[2] % addQuote(args[3])
73
+ except Exception as e:
74
+ pass
75
+ # print('instrument_sqlalchemy_engine 2:', query)
76
+ try:
77
+ if not query:
78
+ query = args[2].decode()
79
+ except Exception as e:
80
+ query = str(args[2])
81
+ # print('instrument_sqlalchemy_engine 3:', query)
82
+ sqlalchemy_track_skip = False
83
+ except_track_module = ['pymysql', 'mysqldb', 'psycopg2','psycopg','sqlite3']
84
+
85
+ # 이미 DB드라이브 추적 모듈이 존재하면 sqlAlhechmy 의 추적 중지
86
+ if any(keyword in str(args[0]) for keyword in except_track_module):
87
+ sqlalchemy_track_skip = True
88
+
89
+ if (not query) or (sqlalchemy_track_skip):
90
+ return fn(*args, **kwargs)
91
+
92
+ start_time = DateUtil.nowSystem()
93
+ ctx.start_time = start_time
94
+ ctx.active_sqlhash = query
95
+ # print('instrument_sqlalchemy_engine 4')
96
+ try:
97
+ callback = fn(*args, **kwargs)
98
+ # print('instrument_sqlalchemy_engine 5')
99
+ return callback
100
+ except Exception as e:
101
+ interceptor_step_error(e)
102
+ finally:
103
+ # print('instrument_sqlalchemy_engine 6')
104
+ ctx = TraceContextManager.getLocalContext()
105
+ if ctx:
106
+ datas = [ctx.lctx.get('dbc', ''), query, 0]
107
+ ctx.elapsed = DateUtil.nowSystem() - start_time
108
+ # print('instrument_sqlalchemy_engine 6.1', datas)
109
+ async_sender.send_packet(PacketTypeEnum.TX_SQL, ctx,
110
+ datas)
111
+ # print('instrument_sqlalchemy_engine 7')
112
+ if hasattr(cursor, 'rowcount'):
113
+ count = cursor.rowcount
114
+ # print('instrument_sqlalchemy_engine 8')
115
+ if count > -1:
116
+ desc = '{0}: {1}'.format('Fetch count', count)
117
+ datas = [' ', ' ', desc]
118
+ ctx.elapsed = 0
119
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
120
+ ctx.active_sqlhash = 0
121
+
122
+ # print('instrument_sqlalchemy_engine 9')
123
+
124
+ return trace
125
+
126
+ # print('database_toolkit.instrument_sqlalchemy_engine step -1')
127
+ if hasattr(module, 'DefaultDialect') and hasattr(module.DefaultDialect, 'do_execute'):
128
+ # print('database_toolkit.instrument_sqlalchemy_engine step -2')
129
+ module.DefaultDialect.do_execute = wrapper(module.DefaultDialect.do_execute)
130
+ module.DefaultDialect.do_executemany = wrapper(module.DefaultDialect.do_executemany)
131
+ module.DefaultDialect.do_execute_no_params = wrapper(module.DefaultDialect.do_execute_no_params)
132
+ # print('database_toolkit.instrument_sqlalchemy_engine step -3')
133
+
134
+ class _urlbase(object):
135
+ def __init__(self, url):
136
+ self.url = url
137
+
138
+ class connect(_urlbase):
139
+ def __call__(self,*args, **kwargs):
140
+ TraceContextManager.addDBPoolIdle(self.url)
141
+ # import os
142
+ # print("connect for ",self.url, os.getpid())
143
+
144
+ class checkin(_urlbase):
145
+ def __call__(self,*args, **kwargs):
146
+ TraceContextManager.addDBPoolIdle(self.url, isCheckIn=True)
147
+ #print("checkin for ",self.url)
148
+
149
+ class checkout(_urlbase):
150
+ def __call__(self,*args, **kwargs):
151
+ TraceContextManager.addDBPoolActive(self.url)
152
+ #print("checkout for ",self.url)
153
+
154
+ class reset(_urlbase):
155
+ def __call__(self,*args, **kwargs):
156
+ #print("reset for ",self.url)
157
+ pass
158
+
159
+ class invalidate(_urlbase):
160
+ def __call__(self,*args, **kwargs):
161
+ #print("invalidate for ",self.url)
162
+ TraceContextManager.removeDBPoolIdle(self.url)
163
+
164
+ class close(_urlbase):
165
+ def __call__(self,*args, **kwargs):
166
+ TraceContextManager.removeDBPoolIdle(self.url)
167
+
168
+ class detach(_urlbase):
169
+ def __call__(self,*args, **kwargs):
170
+ TraceContextManager.removeDBPoolIdle(self.url)
171
+
172
+ class close_detached(_urlbase):
173
+ def __call__(self,*args, **kwargs):
174
+ #print("close_detached for ",self.url)
175
+ pass
176
+
177
+
178
+ def instrument_sqlalchemy_engine_basic(module):
179
+ def wrapper(fn):
180
+ @wraps(fn)
181
+ def trace(*args, **kwargs):
182
+ engine = fn(*args, **kwargs)
183
+ if len(args) > 0:
184
+ url = str(args[0])
185
+ try:
186
+ import sqlalchemy.engine.event as event
187
+
188
+ event.listen(engine, "connect", connect(url))
189
+ event.listen(engine, "checkin", checkin(url))
190
+ event.listen(engine, "checkout", checkout(url))
191
+ event.listen(engine, "close", close(url))
192
+ except Exception as e:
193
+ pass
194
+ try:
195
+ import sqlalchemy.event.api as event
196
+
197
+ event.listen(engine, "connect", connect(url))
198
+ event.listen(engine, "checkin", checkin(url))
199
+ event.listen(engine, "checkout", checkout(url))
200
+
201
+ #event.listen(engine, "reset", reset(url))
202
+ #event.listen(engine, "invalidate", invalidate(url))
203
+ event.listen(engine, "close", close(url))
204
+ #event.listen(engine, "detach", detach(url))
205
+ #event.listen(engine, "close_detached", close_detached(url))
206
+
207
+ except Exception as e:
208
+ pass
209
+ return engine
210
+
211
+ return trace
212
+ if hasattr(module, "create_engine"):
213
+ module.create_engine = wrapper(module.create_engine)