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.
- whatap/LICENSE +0 -0
- whatap/README.rst +49 -0
- whatap/__init__.py +923 -0
- whatap/__main__.py +4 -0
- whatap/agent/darwin/amd64/whatap_python +0 -0
- whatap/agent/darwin/arm64/whatap_python +0 -0
- whatap/agent/linux/amd64/whatap_python +0 -0
- whatap/agent/linux/arm64/whatap_python +0 -0
- whatap/agent/windows/whatap_python.exe +0 -0
- whatap/bootstrap/__init__.py +0 -0
- whatap/bootstrap/sitecustomize.py +19 -0
- whatap/build.py +4 -0
- whatap/conf/__init__.py +0 -0
- whatap/conf/configuration.py +280 -0
- whatap/conf/configure.py +105 -0
- whatap/conf/license.py +49 -0
- whatap/control/__init__.py +0 -0
- whatap/counter/__init__.py +14 -0
- whatap/counter/counter_manager.py +45 -0
- whatap/counter/tasks/__init__.py +3 -0
- whatap/counter/tasks/base_task.py +26 -0
- whatap/counter/tasks/llm_evaluator_task.py +501 -0
- whatap/counter/tasks/llm_log_sink_task.py +309 -0
- whatap/counter/tasks/llm_stat_task.py +78 -0
- whatap/counter/tasks/openfiledescriptor.py +67 -0
- whatap/io/__init__.py +1 -0
- whatap/io/data_inputx.py +161 -0
- whatap/io/data_outputx.py +262 -0
- whatap/llm/__init__.py +17 -0
- whatap/llm/definitions.py +43 -0
- whatap/llm/evaluators/__init__.py +136 -0
- whatap/llm/evaluators/base.py +114 -0
- whatap/llm/evaluators/builtins/__init__.py +91 -0
- whatap/llm/evaluators/builtins/answer_relevance.py +46 -0
- whatap/llm/evaluators/builtins/combined_judge.py +271 -0
- whatap/llm/evaluators/builtins/factuality.py +71 -0
- whatap/llm/evaluators/builtins/hallucination.py +97 -0
- whatap/llm/evaluators/builtins/llm_judge.py +516 -0
- whatap/llm/evaluators/builtins/pii_leak.py +214 -0
- whatap/llm/evaluators/builtins/prompt_injection.py +71 -0
- whatap/llm/evaluators/builtins/toxicity.py +53 -0
- whatap/llm/evaluators/builtins/url_scan.py +194 -0
- whatap/llm/evaluators/registry.py +192 -0
- whatap/llm/evaluators/sampler.py +83 -0
- whatap/llm/evaluators/scope.py +334 -0
- whatap/llm/features.py +66 -0
- whatap/llm/log_sink_packs/__init__.py +9 -0
- whatap/llm/log_sink_packs/llm_input_message.py +16 -0
- whatap/llm/log_sink_packs/llm_log_sink_pack.py +72 -0
- whatap/llm/log_sink_packs/llm_output_message.py +19 -0
- whatap/llm/log_sink_packs/llm_step_eval_status.py +94 -0
- whatap/llm/log_sink_packs/llm_step_status.py +118 -0
- whatap/llm/log_sink_packs/llm_system_message.py +16 -0
- whatap/llm/log_sink_packs/llm_tool_calls.py +44 -0
- whatap/llm/log_sink_packs/llm_tool_results.py +16 -0
- whatap/llm/log_sink_packs/llm_tx_status.py +108 -0
- whatap/llm/pricing.py +236 -0
- whatap/llm/prompt_meta.py +288 -0
- whatap/llm/providers/__init__.py +0 -0
- whatap/llm/providers/anthropic/__init__.py +37 -0
- whatap/llm/providers/anthropic/messages/__init__.py +0 -0
- whatap/llm/providers/anthropic/messages/messages.py +70 -0
- whatap/llm/providers/anthropic/messages/messages_context.py +76 -0
- whatap/llm/providers/anthropic/messages/messages_extractor.py +126 -0
- whatap/llm/providers/interceptor.py +182 -0
- whatap/llm/providers/openai/__init__.py +133 -0
- whatap/llm/providers/openai/chat/__init__.py +0 -0
- whatap/llm/providers/openai/chat/chat.py +82 -0
- whatap/llm/providers/openai/chat/chat_context.py +78 -0
- whatap/llm/providers/openai/chat/chat_extractor.py +127 -0
- whatap/llm/providers/openai/completions/__init__.py +0 -0
- whatap/llm/providers/openai/completions/completions.py +70 -0
- whatap/llm/providers/openai/completions/completions_context.py +31 -0
- whatap/llm/providers/openai/completions/completions_extractor.py +61 -0
- whatap/llm/providers/openai/content_parser.py +41 -0
- whatap/llm/providers/openai/embeddings/__init__.py +0 -0
- whatap/llm/providers/openai/embeddings/embeddings.py +59 -0
- whatap/llm/providers/openai/embeddings/embeddings_context.py +25 -0
- whatap/llm/providers/openai/embeddings/embeddings_extractor.py +26 -0
- whatap/llm/providers/openai/responses/__init__.py +0 -0
- whatap/llm/providers/openai/responses/responses.py +70 -0
- whatap/llm/providers/openai/responses/responses_context.py +88 -0
- whatap/llm/providers/openai/responses/responses_extractor.py +126 -0
- whatap/llm/providers/stream_accumulator.py +73 -0
- whatap/llm/stats/__init__.py +35 -0
- whatap/llm/stats/active_stat.py +86 -0
- whatap/llm/stats/answer_relevance_eval_stat.py +10 -0
- whatap/llm/stats/api_status_stat.py +35 -0
- whatap/llm/stats/base_stat.py +107 -0
- whatap/llm/stats/combined_judge_eval_stat.py +11 -0
- whatap/llm/stats/error_stat.py +59 -0
- whatap/llm/stats/eval_stat.py +225 -0
- whatap/llm/stats/factuality_eval_stat.py +10 -0
- whatap/llm/stats/feature_stat.py +104 -0
- whatap/llm/stats/finish_stat.py +105 -0
- whatap/llm/stats/hallucination_eval_stat.py +10 -0
- whatap/llm/stats/meter.py +18 -0
- whatap/llm/stats/perf_stat.py +117 -0
- whatap/llm/stats/pii_leak_eval_stat.py +12 -0
- whatap/llm/stats/prompt_injection_eval_stat.py +10 -0
- whatap/llm/stats/token_usage_stat.py +133 -0
- whatap/llm/stats/toxicity_eval_stat.py +10 -0
- whatap/llm/stats/url_scan_eval_stat.py +12 -0
- whatap/net/__init__.py +0 -0
- whatap/net/async_sender.py +107 -0
- whatap/net/packet_enum.py +44 -0
- whatap/net/packet_type_enum.py +31 -0
- whatap/net/param_def.py +69 -0
- whatap/net/stackhelper.py +87 -0
- whatap/net/udp_session.py +394 -0
- whatap/net/udp_thread.py +54 -0
- whatap/pack/__init__.py +0 -0
- whatap/pack/logSinkPack.py +77 -0
- whatap/pack/pack.py +34 -0
- whatap/pack/pack_enum.py +41 -0
- whatap/pack/tagCountPack.py +61 -0
- whatap/scripts/__init__.py +208 -0
- whatap/trace/__init__.py +12 -0
- whatap/trace/mod/__init__.py +0 -0
- whatap/trace/mod/amqp/__init__.py +0 -0
- whatap/trace/mod/amqp/kombu.py +122 -0
- whatap/trace/mod/amqp/pika.py +62 -0
- whatap/trace/mod/application/__init__.py +0 -0
- whatap/trace/mod/application/bottle.py +34 -0
- whatap/trace/mod/application/celery.py +81 -0
- whatap/trace/mod/application/cherrypy.py +30 -0
- whatap/trace/mod/application/django.py +287 -0
- whatap/trace/mod/application/django_asgi.py +266 -0
- whatap/trace/mod/application/django_py3.py +251 -0
- whatap/trace/mod/application/fastapi/__init__.py +31 -0
- whatap/trace/mod/application/fastapi/endpoint.py +73 -0
- whatap/trace/mod/application/fastapi/exception_log.py +63 -0
- whatap/trace/mod/application/fastapi/instrumentation.py +204 -0
- whatap/trace/mod/application/fastapi/scope.py +115 -0
- whatap/trace/mod/application/fastapi/transaction.py +67 -0
- whatap/trace/mod/application/flask.py +52 -0
- whatap/trace/mod/application/frappe.py +224 -0
- whatap/trace/mod/application/graphql.py +170 -0
- whatap/trace/mod/application/nameko.py +39 -0
- whatap/trace/mod/application/odoo.py +63 -0
- whatap/trace/mod/application/starlette.py +126 -0
- whatap/trace/mod/application/tornado.py +163 -0
- whatap/trace/mod/application/wsgi.py +195 -0
- whatap/trace/mod/database/__init__.py +0 -0
- whatap/trace/mod/database/cxoracle.py +49 -0
- whatap/trace/mod/database/mongo.py +169 -0
- whatap/trace/mod/database/mysql.py +80 -0
- whatap/trace/mod/database/neo4j.py +90 -0
- whatap/trace/mod/database/psycopg2.py +45 -0
- whatap/trace/mod/database/psycopg3.py +359 -0
- whatap/trace/mod/database/redis.py +122 -0
- whatap/trace/mod/database/sqlalchemy.py +213 -0
- whatap/trace/mod/database/sqlite3.py +130 -0
- whatap/trace/mod/database/util.py +630 -0
- whatap/trace/mod/email/__init__.py +0 -0
- whatap/trace/mod/email/smtp.py +78 -0
- whatap/trace/mod/httpc/__init__.py +0 -0
- whatap/trace/mod/httpc/django.py +31 -0
- whatap/trace/mod/httpc/httplib.py +70 -0
- whatap/trace/mod/httpc/httpx.py +62 -0
- whatap/trace/mod/httpc/requests.py +20 -0
- whatap/trace/mod/httpc/urllib3.py +27 -0
- whatap/trace/mod/httpc/util.py +388 -0
- whatap/trace/mod/logging.py +161 -0
- whatap/trace/mod/plugin.py +84 -0
- whatap/trace/mod/standalone/__init__.py +0 -0
- whatap/trace/mod/standalone/multiple.py +293 -0
- whatap/trace/mod/standalone/single.py +135 -0
- whatap/trace/simple_trace_context.py +18 -0
- whatap/trace/trace_context.py +212 -0
- whatap/trace/trace_context_manager.py +244 -0
- whatap/trace/trace_error.py +84 -0
- whatap/trace/trace_handler.py +89 -0
- whatap/trace/trace_import.py +91 -0
- whatap/trace/trace_module_definition.py +156 -0
- whatap/util/__init__.py +0 -0
- whatap/util/bit_util.py +49 -0
- whatap/util/cardinality/__init__.py +0 -0
- whatap/util/cardinality/hyperloglog.py +84 -0
- whatap/util/cardinality/murmurhash.py +20 -0
- whatap/util/cardinality/registerset.py +60 -0
- whatap/util/compare_util.py +19 -0
- whatap/util/date_util.py +55 -0
- whatap/util/debug_util.py +73 -0
- whatap/util/escape_literal_sql.py +233 -0
- whatap/util/frame_util.py +20 -0
- whatap/util/hash_util.py +103 -0
- whatap/util/hexa32.py +66 -0
- whatap/util/int_set.py +199 -0
- whatap/util/ip_util.py +63 -0
- whatap/util/keygen.py +11 -0
- whatap/util/linked_list.py +113 -0
- whatap/util/linked_map.py +359 -0
- whatap/util/metering_util.py +103 -0
- whatap/util/request_double_queue.py +68 -0
- whatap/util/request_queue.py +60 -0
- whatap/util/string_util.py +20 -0
- whatap/util/throttle_util.py +99 -0
- whatap/util/userid_util.py +134 -0
- whatap/value/__init__.py +1 -0
- whatap/value/blob_value.py +38 -0
- whatap/value/boolean_value.py +33 -0
- whatap/value/decimal_value.py +36 -0
- whatap/value/double_summary.py +86 -0
- whatap/value/double_value.py +33 -0
- whatap/value/float_array.py +42 -0
- whatap/value/float_value.py +34 -0
- whatap/value/int_array.py +42 -0
- whatap/value/ip4_value.py +50 -0
- whatap/value/list_value.py +105 -0
- whatap/value/long_array.py +44 -0
- whatap/value/long_summary.py +83 -0
- whatap/value/map_value.py +154 -0
- whatap/value/null_value.py +21 -0
- whatap/value/number_value.py +33 -0
- whatap/value/summary_value.py +39 -0
- whatap/value/text_array.py +58 -0
- whatap/value/text_hash_value.py +37 -0
- whatap/value/text_value.py +43 -0
- whatap/value/value.py +26 -0
- whatap/value/value_enum.py +80 -0
- whatap/whatap.conf +14 -0
- whatap_python-2.1.0.dist-info/METADATA +87 -0
- whatap_python-2.1.0.dist-info/RECORD +227 -0
- whatap_python-2.1.0.dist-info/WHEEL +5 -0
- whatap_python-2.1.0.dist-info/entry_points.txt +6 -0
- whatap_python-2.1.0.dist-info/top_level.txt +1 -0
whatap/__init__.py
ADDED
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
import logging as logging_module
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
import sys
|
|
5
|
+
import subprocess, signal
|
|
6
|
+
import time
|
|
7
|
+
from whatap import build
|
|
8
|
+
from whatap.util.date_util import DateUtil
|
|
9
|
+
import threading
|
|
10
|
+
import builtins
|
|
11
|
+
|
|
12
|
+
__version__ = build.version
|
|
13
|
+
|
|
14
|
+
LOGGING_MSG_FORMAT = '[%(asctime)s] : - %(message)s'
|
|
15
|
+
LOGGING_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
|
16
|
+
|
|
17
|
+
logging = logging_module.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
PY2 = sys.version_info[0] == 2
|
|
20
|
+
PY3 = sys.version_info[0] == 3
|
|
21
|
+
|
|
22
|
+
ROOT_DIR = __file__
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ContextFilter(logging_module.Filter):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
super(ContextFilter, self).__init__()
|
|
28
|
+
self.last_id = None
|
|
29
|
+
|
|
30
|
+
def filter(self, record):
|
|
31
|
+
try:
|
|
32
|
+
if record.id:
|
|
33
|
+
if self.last_id == record.id:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
self.last_id = record.id
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
except Exception as e:
|
|
40
|
+
record.id = ''
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
from whatap.conf.configure import Configure as conf
|
|
46
|
+
CONFIG_FILE_NAME = 'whatap.conf'
|
|
47
|
+
LOG_FILE_NAME = 'whatap-hook.log'
|
|
48
|
+
|
|
49
|
+
isFrappeCommands = "get-frappe-commands" in sys.argv if hasattr(sys, "argv") else False
|
|
50
|
+
|
|
51
|
+
def preview_whatap_conf(option_name:str):
|
|
52
|
+
home = os.environ.get('WHATAP_HOME', '.')
|
|
53
|
+
whatap_config = os.path.join(home, 'whatap.conf')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
현재 preview_whatap_conf 를 사용중인 옵션
|
|
58
|
+
- ignore_whatap_stdout (False)
|
|
59
|
+
- standalone_enabled (False)
|
|
60
|
+
- counter_thread_enabled (False)
|
|
61
|
+
"""
|
|
62
|
+
value = None
|
|
63
|
+
try:
|
|
64
|
+
with open(whatap_config) as f:
|
|
65
|
+
for raw in f:
|
|
66
|
+
line = raw.strip()
|
|
67
|
+
if not line or line.startswith('#'):
|
|
68
|
+
continue
|
|
69
|
+
if line.startswith(option_name):
|
|
70
|
+
parts = line.split('=', 1)
|
|
71
|
+
if len(parts) == 2:
|
|
72
|
+
value = parts[1].strip()
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
except FileNotFoundError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f'WHATAP: config parse error ({e!r})')
|
|
80
|
+
|
|
81
|
+
if value is not None:
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
env_value = os.environ.get(option_name)
|
|
85
|
+
if env_value is not None:
|
|
86
|
+
return env_value
|
|
87
|
+
|
|
88
|
+
return 'false'
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
ignore_whatap_stdout = preview_whatap_conf("ignore_whatap_stdout")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
original_print = builtins.print
|
|
97
|
+
|
|
98
|
+
def print_option(func):
|
|
99
|
+
def wrapper(*args, **kwargs):
|
|
100
|
+
|
|
101
|
+
if(ignore_whatap_stdout == 'false'):
|
|
102
|
+
result = func(*args, **kwargs)
|
|
103
|
+
else:
|
|
104
|
+
result = None
|
|
105
|
+
return result
|
|
106
|
+
return wrapper
|
|
107
|
+
|
|
108
|
+
__builtins__ = dict(__builtins__)
|
|
109
|
+
__builtins__['print'] = print_option(original_print)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def whatap_print(*args):
|
|
113
|
+
if isFrappeCommands:
|
|
114
|
+
logging.info(*args)
|
|
115
|
+
else:
|
|
116
|
+
if len(args) > 0:
|
|
117
|
+
message = " ".join(args)
|
|
118
|
+
print(message)
|
|
119
|
+
|
|
120
|
+
class Logger(object):
|
|
121
|
+
def __init__(self):
|
|
122
|
+
self.logger = logging
|
|
123
|
+
self.logger.addFilter(ContextFilter())
|
|
124
|
+
self.handler = None
|
|
125
|
+
|
|
126
|
+
self.create_log()
|
|
127
|
+
|
|
128
|
+
def create_log(self):
|
|
129
|
+
os.environ['WHATAP_LOGS'] = os.path.join(os.environ['WHATAP_HOME'],
|
|
130
|
+
'logs')
|
|
131
|
+
if not os.path.exists(os.environ['WHATAP_LOGS']):
|
|
132
|
+
try:
|
|
133
|
+
os.mkdir(os.environ['WHATAP_LOGS'])
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
whatap_print('WHATAP: LOG FILE WRITE ERROR.')
|
|
137
|
+
whatap_print(
|
|
138
|
+
'WHATAP: Try to execute command. \n {}'.format(
|
|
139
|
+
'sudo mkdir -m 777 -p $WHATAP_HOME/logs`'))
|
|
140
|
+
|
|
141
|
+
self.print_log()
|
|
142
|
+
|
|
143
|
+
def print_log(self):
|
|
144
|
+
try:
|
|
145
|
+
if self.handler:
|
|
146
|
+
self.logger.removeHandler(self.handler)
|
|
147
|
+
|
|
148
|
+
temp_logging_msg_format = '[%(asctime)s] : %(id)s - %(message)s'
|
|
149
|
+
logging_format = logging_module.Formatter(
|
|
150
|
+
fmt=temp_logging_msg_format, datefmt=LOGGING_DATE_FORMAT)
|
|
151
|
+
|
|
152
|
+
fh = logging_module.FileHandler(
|
|
153
|
+
os.path.join(os.environ['WHATAP_LOGS'], LOG_FILE_NAME))
|
|
154
|
+
fh.setFormatter(logging_format)
|
|
155
|
+
self.logger.addHandler(fh)
|
|
156
|
+
self.handler = fh
|
|
157
|
+
|
|
158
|
+
self.logger.setLevel(logging_module.DEBUG)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
whatap_print('WHATAP: LOGGING ERROR: {}'.format(e))
|
|
161
|
+
else:
|
|
162
|
+
self.print_whatap()
|
|
163
|
+
|
|
164
|
+
def print_whatap(self):
|
|
165
|
+
str = '\n' + \
|
|
166
|
+
' _ ____ ______' + build.app + '-AGENT \n' + \
|
|
167
|
+
'| | /| / / / ___ /_ __/__ ____' + '\n' + \
|
|
168
|
+
'| |/ |/ / _ \\/ _ `// / / _ `/ _ \\' + '\n' + \
|
|
169
|
+
'|__/|__/_//_/\\_,_//_/ \\_,_/ .__/' + '\n' + \
|
|
170
|
+
' /_/' + '\n' + \
|
|
171
|
+
'Just Tap, Always Monitoring' + '\n' + \
|
|
172
|
+
'WhaTap ' + build.app + ' Agent version ' + build.version + ', ' + build.release_date + '\n\n'
|
|
173
|
+
|
|
174
|
+
str += '{0}: {1}\n'.format('WHATAP_HOME', os.environ['WHATAP_HOME'])
|
|
175
|
+
str += '{0}: {1}\n'.format('Config', os.path.join(os.environ['WHATAP_HOME'],
|
|
176
|
+
os.environ['WHATAP_CONFIG']))
|
|
177
|
+
str += '{0}: {1}\n\n'.format('Logs', os.environ['WHATAP_LOGS'])
|
|
178
|
+
|
|
179
|
+
whatap_print(str)
|
|
180
|
+
logging.debug(str)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def read_file(home, file_name):
|
|
184
|
+
data = ''
|
|
185
|
+
try:
|
|
186
|
+
f = open(os.path.join(os.environ.get(home), file_name), 'r+')
|
|
187
|
+
data = str(f.readline()).strip()
|
|
188
|
+
f.close()
|
|
189
|
+
finally:
|
|
190
|
+
return data
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def write_file(home, file_name, value):
|
|
194
|
+
try:
|
|
195
|
+
f = open(os.path.join(os.environ.get(home), file_name), 'w+')
|
|
196
|
+
f.write(value)
|
|
197
|
+
f.close()
|
|
198
|
+
except Exception as e:
|
|
199
|
+
whatap_print('WHATAP: WHATAP HOME ERROR. (path: {})'.format(os.path.join(os.environ.get(home))))
|
|
200
|
+
whatap_print(
|
|
201
|
+
'WHATAP: Try to execute command. \n {}'.format(
|
|
202
|
+
'`sudo chmod -R 777 $WHATAP_HOME`'))
|
|
203
|
+
return False
|
|
204
|
+
else:
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def check_whatap_home(target='WHATAP_HOME'):
|
|
209
|
+
whatap_home = os.environ.get(target)
|
|
210
|
+
if not whatap_home:
|
|
211
|
+
whatap_home = find_whatap_conf()
|
|
212
|
+
if not whatap_home:
|
|
213
|
+
whatap_print('WHATAP: ${} is empty'.format(target))
|
|
214
|
+
|
|
215
|
+
return whatap_home
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def init_config(home):
|
|
219
|
+
whatap_home = os.environ.get(home)
|
|
220
|
+
if not whatap_home:
|
|
221
|
+
whatap_home = find_whatap_conf()
|
|
222
|
+
if not whatap_home:
|
|
223
|
+
whatap_home = read_file(home, home.lower())
|
|
224
|
+
if not whatap_home:
|
|
225
|
+
whatap_home = os.getcwd()
|
|
226
|
+
os.environ[home] = whatap_home
|
|
227
|
+
|
|
228
|
+
whatap_print('WHATAP: WHATAP_HOME is empty')
|
|
229
|
+
whatap_print(
|
|
230
|
+
'WHATAP: WHATAP_HOME set default CURRENT_WORKING_DIRECTORY value')
|
|
231
|
+
whatap_print('CURRENT_WORKING_DIRECTORY is {}\n'.format(whatap_home))
|
|
232
|
+
|
|
233
|
+
if not write_file(home, home.lower(), whatap_home):
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
os.environ[home] = whatap_home
|
|
237
|
+
config_file = os.path.join(os.environ[home],
|
|
238
|
+
CONFIG_FILE_NAME)
|
|
239
|
+
|
|
240
|
+
if not os.path.exists(config_file):
|
|
241
|
+
with open(
|
|
242
|
+
os.path.join(os.path.dirname(__file__),
|
|
243
|
+
CONFIG_FILE_NAME),
|
|
244
|
+
'r') as f:
|
|
245
|
+
content = f.read()
|
|
246
|
+
try:
|
|
247
|
+
with open(config_file, 'w+') as new_f:
|
|
248
|
+
new_f.write(content)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
whatap_print('WHATAP: PERMISSION ERROR: {}'.format(e))
|
|
251
|
+
whatap_print(
|
|
252
|
+
'WHATAP: Try to execute command. \n {}'.format(
|
|
253
|
+
'`sudo chmod -R 777 $WHATAP_HOME`'))
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def update_config(home, opt_key, opt_value):
|
|
260
|
+
config_file = os.path.join(os.environ[home],
|
|
261
|
+
CONFIG_FILE_NAME)
|
|
262
|
+
try:
|
|
263
|
+
with open(config_file, 'r+') as f:
|
|
264
|
+
is_update = False
|
|
265
|
+
content = ''
|
|
266
|
+
for line in f:
|
|
267
|
+
if line:
|
|
268
|
+
key = line.split('=')[0].strip()
|
|
269
|
+
if key == opt_key:
|
|
270
|
+
is_update = True
|
|
271
|
+
line = '{0}={1}\n'.format(key, opt_value)
|
|
272
|
+
|
|
273
|
+
content += line
|
|
274
|
+
if not is_update:
|
|
275
|
+
content += '\n{0}={1}\n'.format(opt_key, opt_value)
|
|
276
|
+
open(config_file, 'w+').write(content)
|
|
277
|
+
|
|
278
|
+
except Exception as e:
|
|
279
|
+
whatap_print('WHATAP: OPTION ERROR: {}'.format(e))
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def config(home):
|
|
283
|
+
os.environ['WHATAP_CONFIG'] = CONFIG_FILE_NAME
|
|
284
|
+
|
|
285
|
+
from whatap.conf.configure import Configure as conf
|
|
286
|
+
if conf.init():
|
|
287
|
+
from whatap.net.packet_enum import PacketEnum
|
|
288
|
+
PacketEnum.PORT = int(conf.net_udp_port)
|
|
289
|
+
|
|
290
|
+
if getattr(conf, 'apm_enabled', False):
|
|
291
|
+
from whatap.conf.license import License
|
|
292
|
+
conf.PCODE = License.getProjectCode(conf.license)
|
|
293
|
+
|
|
294
|
+
import whatap.counter
|
|
295
|
+
|
|
296
|
+
hooks(home)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
from whatap.trace.trace_import import ImportFinder
|
|
300
|
+
from whatap.trace.trace_module_definition import DEFINITION, IMPORT_HOOKS, \
|
|
301
|
+
PLUGIN
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _build_ignore_instrumentation_set():
|
|
305
|
+
"""whatap.conf 의 ignore_instrumentation_set 값을 정규화하여 반환.
|
|
306
|
+
Configure.load() 가 _set 접미사 키를 콤마 split 해 list 로 만들지만,
|
|
307
|
+
환경변수/직접 setProperty 등 다른 경로로 들어온 경우(str)도 함께 처리한다.
|
|
308
|
+
"""
|
|
309
|
+
raw = getattr(conf, 'ignore_instrumentation_set', None) or []
|
|
310
|
+
if isinstance(raw, str):
|
|
311
|
+
raw = raw.split(',')
|
|
312
|
+
return {str(item).strip().lower() for item in raw if item and str(item).strip()}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _is_instrumentation_ignored(definition_key, ignore_set):
|
|
316
|
+
"""DEFINITION 키(예: 'database.redis', 'logging')가 무시 대상인지 판정.
|
|
317
|
+
|
|
318
|
+
매칭 규칙(셋 중 하나라도 맞으면 True):
|
|
319
|
+
1) 키 전체가 일치 예: 'database.redis' == 'database.redis'
|
|
320
|
+
2) 카테고리(점 앞)와 일치 예: 'database' → 'database.*' 모두
|
|
321
|
+
3) 라이브러리(점 뒤)와 일치 예: 'redis' → 'database.redis'
|
|
322
|
+
"""
|
|
323
|
+
if not ignore_set:
|
|
324
|
+
return False
|
|
325
|
+
key_lower = definition_key.lower()
|
|
326
|
+
if key_lower in ignore_set:
|
|
327
|
+
return True
|
|
328
|
+
if '.' in key_lower:
|
|
329
|
+
category, lib = key_lower.split('.', 1)
|
|
330
|
+
if category in ignore_set or lib in ignore_set:
|
|
331
|
+
return True
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def hooks(home):
|
|
336
|
+
ignore_set = _build_ignore_instrumentation_set()
|
|
337
|
+
skipped_keys = []
|
|
338
|
+
try:
|
|
339
|
+
for key, value_list in DEFINITION.items():
|
|
340
|
+
if _is_instrumentation_ignored(key, ignore_set):
|
|
341
|
+
skipped_keys.append(key)
|
|
342
|
+
continue
|
|
343
|
+
for value in value_list:
|
|
344
|
+
if len(value) >= 3 and isinstance(value[2], str):
|
|
345
|
+
module_path = value[2]
|
|
346
|
+
elif len(value) == 3 and not value[2]:
|
|
347
|
+
continue
|
|
348
|
+
else:
|
|
349
|
+
module_path = '{0}.{1}.{2}.{3}'.format(
|
|
350
|
+
'whatap', 'trace', 'mod', key)
|
|
351
|
+
IMPORT_HOOKS[value[0]] = {'def': value[1],
|
|
352
|
+
'module': module_path}
|
|
353
|
+
except Exception as e:
|
|
354
|
+
logging.debug(e, extra={'id': 'MODULE ERROR'})
|
|
355
|
+
finally:
|
|
356
|
+
try:
|
|
357
|
+
if skipped_keys:
|
|
358
|
+
logging.debug(
|
|
359
|
+
'WHATAP: ignore_instrumentation_set skipped: {}'.format(
|
|
360
|
+
','.join(skipped_keys)),
|
|
361
|
+
extra={'id': 'WA_IGN'})
|
|
362
|
+
|
|
363
|
+
if conf.trace_logging_enabled and not _is_instrumentation_ignored(
|
|
364
|
+
'logging', ignore_set):
|
|
365
|
+
logging_module = sys.modules.get("logging")
|
|
366
|
+
from whatap.trace.mod.logging import instrument_logging
|
|
367
|
+
instrument_logging(logging_module)
|
|
368
|
+
|
|
369
|
+
if conf.hook_method_patterns:
|
|
370
|
+
from whatap.trace.mod.plugin import instrument_plugin
|
|
371
|
+
patterns = conf.hook_method_patterns.split(',')
|
|
372
|
+
for pattern in patterns:
|
|
373
|
+
pattern=pattern.strip()
|
|
374
|
+
|
|
375
|
+
module_name, class_def = pattern.split(':')
|
|
376
|
+
if not PLUGIN.get(module_name):
|
|
377
|
+
PLUGIN[module_name] = []
|
|
378
|
+
PLUGIN[module_name].append(class_def)
|
|
379
|
+
|
|
380
|
+
DEFINITION["plugin"].append(
|
|
381
|
+
(module_name, 'instrument_plugin'))
|
|
382
|
+
|
|
383
|
+
key = 'plugin'
|
|
384
|
+
for value in DEFINITION[key]:
|
|
385
|
+
IMPORT_HOOKS[value[0]] = {'def': value[1],
|
|
386
|
+
'module': '{0}.{1}.{2}.{3}'.format(
|
|
387
|
+
'whatap',
|
|
388
|
+
'trace',
|
|
389
|
+
'mod',
|
|
390
|
+
key)}
|
|
391
|
+
|
|
392
|
+
if conf.standalone_enabled:
|
|
393
|
+
if conf.standalone_type == 'multiple-transaction':
|
|
394
|
+
from whatap.trace.mod.standalone.multiple import instrument_standalone_multiple
|
|
395
|
+
instrument_standalone_multiple()
|
|
396
|
+
else:
|
|
397
|
+
from whatap.trace.mod.standalone.single import instrument_standalone_single
|
|
398
|
+
instrument_standalone_single()
|
|
399
|
+
|
|
400
|
+
except Exception as e:
|
|
401
|
+
logging.debug(e, extra={'id': 'PLUGIN ERROR'})
|
|
402
|
+
finally:
|
|
403
|
+
sys.meta_path.insert(0, ImportFinder())
|
|
404
|
+
logging.debug('WHATAP AGENT START!', extra={'id': 'WA000'})
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def agent():
|
|
408
|
+
home = 'WHATAP_HOME'
|
|
409
|
+
whatap_home = os.environ.get(home)
|
|
410
|
+
if not whatap_home:
|
|
411
|
+
whatap_home = read_file(home, home.lower())
|
|
412
|
+
if not whatap_home:
|
|
413
|
+
whatap_home = os.getcwd()
|
|
414
|
+
os.environ[home] = whatap_home
|
|
415
|
+
|
|
416
|
+
whatap_print('WHATAP: WHATAP_HOME is empty')
|
|
417
|
+
whatap_print(
|
|
418
|
+
'WHATAP: WHATAP_HOME set default CURRENT_WORKING_DIRECTORY value')
|
|
419
|
+
whatap_print('CURRENT_WORKING_DIRECTORY is {}\n'.format(whatap_home))
|
|
420
|
+
|
|
421
|
+
if write_file(home, home.lower(), whatap_home):
|
|
422
|
+
os.environ['WHATAP_HOME'] = whatap_home
|
|
423
|
+
|
|
424
|
+
whatap_code_start = preview_whatap_conf("whatap_code_start")
|
|
425
|
+
if whatap_code_start == 'true':
|
|
426
|
+
apm_enabled = preview_whatap_conf("apm_enabled")
|
|
427
|
+
llm_enabled = preview_whatap_conf("llm_enabled")
|
|
428
|
+
|
|
429
|
+
if apm_enabled == 'true':
|
|
430
|
+
t = threading.Thread(target=go)
|
|
431
|
+
t.setDaemon(True)
|
|
432
|
+
t.start()
|
|
433
|
+
|
|
434
|
+
if llm_enabled == 'true':
|
|
435
|
+
t = threading.Thread(target=go, kwargs={'llm': True})
|
|
436
|
+
t.setDaemon(True)
|
|
437
|
+
t.start()
|
|
438
|
+
|
|
439
|
+
config(home)
|
|
440
|
+
|
|
441
|
+
ARCH = {
|
|
442
|
+
'x86_64': 'amd64',
|
|
443
|
+
'x86': '386',
|
|
444
|
+
'x86_32': '386',
|
|
445
|
+
'ARM': 'arm',
|
|
446
|
+
'AArch64': 'arm64',
|
|
447
|
+
'arm64': 'arm64',
|
|
448
|
+
'aarch64': 'arm64'
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
AGENT_NAME = 'whatap_python'
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def go(batch=False, opts={}, llm=False):
|
|
456
|
+
newenv=os.environ.copy()
|
|
457
|
+
newenv['WHATAP_VERSION'] = build.version
|
|
458
|
+
newenv['whatap.start'] = str(DateUtil.now())
|
|
459
|
+
newenv['python.uptime'] = str(DateUtil.datetime())
|
|
460
|
+
newenv['python.version'] = sys.version
|
|
461
|
+
newenv['python.tzname'] = time.tzname[0]
|
|
462
|
+
newenv['os.release'] = platform.release()
|
|
463
|
+
newenv['sys.version_info'] = str(sys.version_info)
|
|
464
|
+
newenv['sys.executable'] = sys.executable
|
|
465
|
+
newenv['sys.path'] = str(sys.path)
|
|
466
|
+
newenv.update(opts)
|
|
467
|
+
|
|
468
|
+
if llm:
|
|
469
|
+
home = 'WHATAP_HOME'
|
|
470
|
+
file_name = AGENT_NAME + '.pid.llm'
|
|
471
|
+
elif not batch:
|
|
472
|
+
home = 'WHATAP_HOME'
|
|
473
|
+
file_name = AGENT_NAME + '.pid'
|
|
474
|
+
else:
|
|
475
|
+
home = 'WHATAP_HOME_BATCH'
|
|
476
|
+
file_name = AGENT_NAME + '.pid.batch'
|
|
477
|
+
|
|
478
|
+
def get_pname(pid):
|
|
479
|
+
# Linux/Unix: use /proc filesystem
|
|
480
|
+
if sys.platform != 'win32':
|
|
481
|
+
cmdlinepath = os.path.join('/proc', str(pid), 'cmdline')
|
|
482
|
+
if os.path.exists(cmdlinepath):
|
|
483
|
+
with open(cmdlinepath) as f:
|
|
484
|
+
content = f.read()
|
|
485
|
+
if content:
|
|
486
|
+
return content.strip()
|
|
487
|
+
else:
|
|
488
|
+
# Windows: use psutil to get process name
|
|
489
|
+
try:
|
|
490
|
+
import psutil
|
|
491
|
+
process = psutil.Process(int(pid))
|
|
492
|
+
return ' '.join(process.cmdline())
|
|
493
|
+
except Exception as e:
|
|
494
|
+
whatap_print('WHATAP: get_pname psutil failed: %s' % e)
|
|
495
|
+
return ''
|
|
496
|
+
|
|
497
|
+
pid = read_file(home, file_name)
|
|
498
|
+
if pid and get_pname(pid).find('whatap_python') >= 0:
|
|
499
|
+
if sys.platform == 'win32':
|
|
500
|
+
# Windows: Don't kill, just skip
|
|
501
|
+
whatap_print('WHATAP: Agent already running (PID: {}). Skipping duplicate execution.'.format(pid))
|
|
502
|
+
return
|
|
503
|
+
else:
|
|
504
|
+
# Unix/Linux: kill and replace existing process
|
|
505
|
+
try:
|
|
506
|
+
import signal
|
|
507
|
+
os.kill(int(pid), signal.SIGKILL)
|
|
508
|
+
except Exception as e:
|
|
509
|
+
whatap_print('WHATAP: SIGKILL failed for pid %s: %s' % (pid, e))
|
|
510
|
+
|
|
511
|
+
try:
|
|
512
|
+
home_path= os.environ.get(home)
|
|
513
|
+
|
|
514
|
+
# Windows uses .exe extension
|
|
515
|
+
agent_binary_name = AGENT_NAME + '.exe' if sys.platform == 'win32' else AGENT_NAME
|
|
516
|
+
dest_agent = os.path.join(home_path, agent_binary_name)
|
|
517
|
+
|
|
518
|
+
# Try to remove existing binary
|
|
519
|
+
# Windows: File may still be locked even after waiting, ignore error and use existing binary
|
|
520
|
+
# Unix/Linux: Removal should always succeed
|
|
521
|
+
needs_deployment = False
|
|
522
|
+
if os.path.exists(dest_agent):
|
|
523
|
+
try:
|
|
524
|
+
os.remove(dest_agent)
|
|
525
|
+
needs_deployment = True
|
|
526
|
+
except (OSError, PermissionError) as e:
|
|
527
|
+
if sys.platform == 'win32':
|
|
528
|
+
# Windows: File is still locked, use existing binary
|
|
529
|
+
pass
|
|
530
|
+
else:
|
|
531
|
+
# Unix/Linux: This should not happen
|
|
532
|
+
raise
|
|
533
|
+
else:
|
|
534
|
+
needs_deployment = True
|
|
535
|
+
|
|
536
|
+
# Windows doesn't use architecture subdirectories (only single x64 binary for now)
|
|
537
|
+
if sys.platform == 'win32':
|
|
538
|
+
source_cwd = os.path.join(os.path.dirname(__file__), 'agent', 'windows')
|
|
539
|
+
else:
|
|
540
|
+
source_cwd = os.path.join(os.path.join(os.path.dirname(__file__), 'agent'), platform.system().lower(),
|
|
541
|
+
ARCH[platform.machine()])
|
|
542
|
+
|
|
543
|
+
source_agent = os.path.join(source_cwd, agent_binary_name)
|
|
544
|
+
|
|
545
|
+
# Deploy agent binary only if needed
|
|
546
|
+
if needs_deployment:
|
|
547
|
+
# Try symlink first (Unix/Linux or Windows with proper permissions)
|
|
548
|
+
try:
|
|
549
|
+
os.symlink(source_agent, dest_agent)
|
|
550
|
+
except (OSError, NotImplementedError):
|
|
551
|
+
# Fallback to copy on Windows or when symlink fails
|
|
552
|
+
import shutil
|
|
553
|
+
shutil.copy2(source_agent, dest_agent)
|
|
554
|
+
# Make executable on Unix-like systems
|
|
555
|
+
if sys.platform != 'win32':
|
|
556
|
+
os.chmod(dest_agent, 0o755)
|
|
557
|
+
|
|
558
|
+
sockfile_path = os.path.join(home_path, 'run')
|
|
559
|
+
if not os.path.exists(sockfile_path):
|
|
560
|
+
os.mkdir(sockfile_path)
|
|
561
|
+
newenv['whatap.enabled'] = 'True'
|
|
562
|
+
newenv['WHATAP_PID_FILE'] = file_name
|
|
563
|
+
newenv['PYTHON_PARENT_APP_PID'] = str(os.getpid())
|
|
564
|
+
|
|
565
|
+
# Windows uses absolute path, Unix/Linux uses relative path
|
|
566
|
+
if sys.platform == 'win32':
|
|
567
|
+
agent_cmd = os.path.join(home_path, agent_binary_name)
|
|
568
|
+
else:
|
|
569
|
+
agent_cmd = './{0}'.format(agent_binary_name)
|
|
570
|
+
|
|
571
|
+
# Windows: Run in background with DETACHED_PROCESS
|
|
572
|
+
# Unix/Linux: Run in background with start_new_session
|
|
573
|
+
# 실행 인자 구성
|
|
574
|
+
cmd_args = [agent_cmd, '-t', '3']
|
|
575
|
+
if llm:
|
|
576
|
+
cmd_args.append('--llm')
|
|
577
|
+
|
|
578
|
+
label = 'LLM golang module' if llm else 'golang module'
|
|
579
|
+
|
|
580
|
+
if sys.platform == 'win32':
|
|
581
|
+
# SESSIONNAME 환경변수는 NSSM이 부모 세션에서 상속받아 부정확함
|
|
582
|
+
# ProcessIdToSessionId로 실제 프로세스 세션 ID 확인
|
|
583
|
+
try:
|
|
584
|
+
import ctypes
|
|
585
|
+
_sid = ctypes.c_ulong(0)
|
|
586
|
+
ctypes.windll.kernel32.ProcessIdToSessionId(
|
|
587
|
+
ctypes.windll.kernel32.GetCurrentProcessId(),
|
|
588
|
+
ctypes.byref(_sid)
|
|
589
|
+
)
|
|
590
|
+
is_session0 = (_sid.value == 0)
|
|
591
|
+
except Exception:
|
|
592
|
+
is_session0 = not os.environ.get('SESSIONNAME')
|
|
593
|
+
|
|
594
|
+
cmd_args.append('foreground')
|
|
595
|
+
|
|
596
|
+
if is_session0:
|
|
597
|
+
# Session 0에서만 WHATAP_FOREGROUND=1 추가 주입.
|
|
598
|
+
# IsAnInteractiveSession()이 false로 떨어지는 경로에서
|
|
599
|
+
# foreground CLI 인자를 보강하는 안전망.
|
|
600
|
+
newenv['WHATAP_FOREGROUND'] = '1'
|
|
601
|
+
|
|
602
|
+
# Session 0에서는 PIPE를 읽지 않으면 버퍼 고갈로 Go 프로세스가 블로킹됨
|
|
603
|
+
# 로그 파일로 리디렉션
|
|
604
|
+
# CREATE_NO_WINDOW: Session 0 서비스 컨텍스트에 적합한 플래그
|
|
605
|
+
CREATE_NO_WINDOW = 0x08000000
|
|
606
|
+
log_dir = os.path.join(home_path, 'logs')
|
|
607
|
+
go_log_path = os.path.join(log_dir, AGENT_NAME + '.log')
|
|
608
|
+
try:
|
|
609
|
+
go_out = open(go_log_path, 'ab')
|
|
610
|
+
except Exception:
|
|
611
|
+
go_out = subprocess.DEVNULL
|
|
612
|
+
process = subprocess.Popen(cmd_args,
|
|
613
|
+
cwd=home_path, env=newenv,
|
|
614
|
+
creationflags=CREATE_NO_WINDOW,
|
|
615
|
+
stdout=go_out, stderr=go_out)
|
|
616
|
+
else:
|
|
617
|
+
DETACHED_PROCESS = 0x00000008
|
|
618
|
+
process = subprocess.Popen(cmd_args,
|
|
619
|
+
cwd=home_path, env=newenv,
|
|
620
|
+
creationflags=DETACHED_PROCESS,
|
|
621
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
622
|
+
else:
|
|
623
|
+
process = subprocess.Popen(cmd_args,
|
|
624
|
+
cwd=home_path, env=newenv,
|
|
625
|
+
start_new_session=True,
|
|
626
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
627
|
+
|
|
628
|
+
time.sleep(0.5)
|
|
629
|
+
if process.poll() is not None:
|
|
630
|
+
if sys.platform == 'win32' and is_session0:
|
|
631
|
+
whatap_print("executed {} (exit code: {}, log: {})".format(label, process.returncode, go_log_path))
|
|
632
|
+
else:
|
|
633
|
+
stdouts, errs = process.communicate()
|
|
634
|
+
whatap_print("executed {} ".format(label), str(stdouts,"utf8"), str(errs, "utf8"))
|
|
635
|
+
else:
|
|
636
|
+
write_file(home, file_name, str(process.pid))
|
|
637
|
+
whatap_print("executed {} in background (PID: {})".format(label, process.pid))
|
|
638
|
+
except Exception as e:
|
|
639
|
+
import traceback
|
|
640
|
+
traceback.print_exc()
|
|
641
|
+
whatap_print('WHATAP: AGENT ERROR: {}'.format(e))
|
|
642
|
+
else:
|
|
643
|
+
whatap_print('WHATAP: AGENT UP! (process name: {}{})\n'.format(AGENT_NAME, ' --llm' if llm else ''))
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
import signal
|
|
647
|
+
|
|
648
|
+
from whatap.trace.trace_handler import trace_handler
|
|
649
|
+
from whatap.trace.trace_error import interceptor_step_error
|
|
650
|
+
from whatap.trace.mod.application.wsgi import interceptor, start_interceptor, \
|
|
651
|
+
end_interceptor
|
|
652
|
+
from whatap.trace.mod.application.fastapi import interceptor_error_log
|
|
653
|
+
from whatap.trace.trace_context import TraceContext, TraceContextManager
|
|
654
|
+
|
|
655
|
+
def register_app(fn):
|
|
656
|
+
@trace_handler(fn, True)
|
|
657
|
+
def trace(*args, **kwargs):
|
|
658
|
+
callback = None
|
|
659
|
+
try:
|
|
660
|
+
environ = args[0]
|
|
661
|
+
callback = interceptor((fn, environ), *args, **kwargs)
|
|
662
|
+
except Exception as e:
|
|
663
|
+
logging.debug('WHATAP(@register_app): ' + str(e),
|
|
664
|
+
extra={'id': 'WA777'}, exc_info=True)
|
|
665
|
+
finally:
|
|
666
|
+
return callback if callback else fn(*args, **kwargs)
|
|
667
|
+
|
|
668
|
+
if not os.environ.get('whatap.enabled'):
|
|
669
|
+
agent()
|
|
670
|
+
|
|
671
|
+
return trace
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def method_profiling(fn):
|
|
675
|
+
def trace(*args, **kwargs):
|
|
676
|
+
callback = None
|
|
677
|
+
try:
|
|
678
|
+
ctx = TraceContext()
|
|
679
|
+
ctx.service_name=fn.__name__
|
|
680
|
+
start_interceptor(ctx)
|
|
681
|
+
callback = fn(*args, **kwargs)
|
|
682
|
+
except Exception as e:
|
|
683
|
+
ctx = TraceContextManager.getLocalContext()
|
|
684
|
+
interceptor_step_error(e, ctx=ctx)
|
|
685
|
+
interceptor_error_log(ctx.id, e, fn, args, kwargs)
|
|
686
|
+
logging.debug('WHATAP(@method_profiling): ' + str(e),
|
|
687
|
+
extra={'id': 'WA776'}, exc_info=True)
|
|
688
|
+
finally:
|
|
689
|
+
ctx = TraceContextManager.getLocalContext()
|
|
690
|
+
end_interceptor(ctx=ctx)
|
|
691
|
+
return callback
|
|
692
|
+
|
|
693
|
+
if not os.environ.get('whatap.enabled'):
|
|
694
|
+
agent()
|
|
695
|
+
|
|
696
|
+
return trace
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def batch_agent():
|
|
700
|
+
home = 'WHATAP_HOME_BATCH'
|
|
701
|
+
batch_home = os.environ.get(home)
|
|
702
|
+
if not batch_home:
|
|
703
|
+
if not read_file(home, home.lower()):
|
|
704
|
+
whatap_print('WHATAP: WHATAP_HOME_BATCH is empty')
|
|
705
|
+
return
|
|
706
|
+
|
|
707
|
+
if write_file(home, home.lower(), batch_home):
|
|
708
|
+
os.environ['WHATAP_HOME_BATCH'] = batch_home
|
|
709
|
+
os.environ['WHATAP_HOME'] = batch_home
|
|
710
|
+
go(batch=True)
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def batch_profiling(fn):
|
|
714
|
+
import inspect
|
|
715
|
+
frame = inspect.stack()[1]
|
|
716
|
+
module = inspect.getmodule(frame[0])
|
|
717
|
+
|
|
718
|
+
def trace(*args, **kwargs):
|
|
719
|
+
if not os.environ.get('whatap.batch.enabled'):
|
|
720
|
+
home = 'WHATAP_HOME_BATCH'
|
|
721
|
+
batch_home = read_file(home, home.lower())
|
|
722
|
+
if not batch_home:
|
|
723
|
+
whatap_print(
|
|
724
|
+
'WHATAP(@batch_profiling): try, whatap-start-batch')
|
|
725
|
+
return fn(*args, **kwargs)
|
|
726
|
+
else:
|
|
727
|
+
os.environ['whatap.batch.enabled'] = 'True'
|
|
728
|
+
os.environ['WHATAP_HOME_BATCH'] = batch_home
|
|
729
|
+
os.environ['WHATAP_HOME'] = batch_home
|
|
730
|
+
config(home)
|
|
731
|
+
|
|
732
|
+
callback = None
|
|
733
|
+
try:
|
|
734
|
+
ctx = TraceContext()
|
|
735
|
+
ctx.service_name=os.path.basename(module.__file__)
|
|
736
|
+
ctx = start_interceptor(ctx)
|
|
737
|
+
|
|
738
|
+
callback = fn(*args, **kwargs)
|
|
739
|
+
end_interceptor(thread_id=ctx.thread_id)
|
|
740
|
+
except Exception as e:
|
|
741
|
+
logging.debug('WHATAP(@batch_profiling): ' + str(e),
|
|
742
|
+
extra={'id': 'WA777'}, exc_info=True)
|
|
743
|
+
finally:
|
|
744
|
+
return callback if callback else fn(*args, **kwargs)
|
|
745
|
+
|
|
746
|
+
return trace
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
import os, time
|
|
750
|
+
import errno
|
|
751
|
+
import sys
|
|
752
|
+
|
|
753
|
+
try:
|
|
754
|
+
import fcntl
|
|
755
|
+
HAS_FCNTL = True
|
|
756
|
+
except ImportError:
|
|
757
|
+
# fcntl is not available on Windows
|
|
758
|
+
HAS_FCNTL = False
|
|
759
|
+
if sys.platform == 'win32':
|
|
760
|
+
try:
|
|
761
|
+
import msvcrt
|
|
762
|
+
HAS_MSVCRT = True
|
|
763
|
+
except ImportError:
|
|
764
|
+
HAS_MSVCRT = False
|
|
765
|
+
else:
|
|
766
|
+
HAS_MSVCRT = False
|
|
767
|
+
|
|
768
|
+
# Windows uses different default temp path
|
|
769
|
+
if sys.platform == 'win32':
|
|
770
|
+
import tempfile
|
|
771
|
+
default_lock_file = os.path.join(tempfile.gettempdir(), 'whatap-python.lock')
|
|
772
|
+
default_llm_lock_file = os.path.join(tempfile.gettempdir(), 'whatap-python-llm.lock')
|
|
773
|
+
else:
|
|
774
|
+
default_lock_file = '/tmp/whatap-python.lock'
|
|
775
|
+
default_llm_lock_file = '/tmp/whatap-python-llm.lock'
|
|
776
|
+
|
|
777
|
+
def openPortFile(filepath=os.environ.get('WHATAP_LOCK_FILE', default_lock_file)):
|
|
778
|
+
f = None
|
|
779
|
+
i=0
|
|
780
|
+
while f == None and i < 100:
|
|
781
|
+
try:
|
|
782
|
+
f = open(filepath, 'r+')
|
|
783
|
+
except IOError as e:
|
|
784
|
+
if e.errno == 2:
|
|
785
|
+
prefix = os.path.split(filepath)[0]
|
|
786
|
+
try:
|
|
787
|
+
if not os.path.exists(prefix):
|
|
788
|
+
os.makedirs(prefix)
|
|
789
|
+
f = open(filepath, 'w+')
|
|
790
|
+
except Exception as e:
|
|
791
|
+
whatap_print('WHATAP: openLockFile makedirs/open failed: %s' % e)
|
|
792
|
+
i += 1
|
|
793
|
+
|
|
794
|
+
if f:
|
|
795
|
+
try:
|
|
796
|
+
if HAS_FCNTL:
|
|
797
|
+
fcntl.lockf(f, fcntl.LOCK_EX)
|
|
798
|
+
elif HAS_MSVCRT:
|
|
799
|
+
# Windows file locking using msvcrt
|
|
800
|
+
try:
|
|
801
|
+
# Use non-blocking lock to avoid OSError
|
|
802
|
+
msvcrt.locking(f.fileno(), msvcrt.LK_NBLCK, 1)
|
|
803
|
+
except OSError:
|
|
804
|
+
# File already locked by another process, skip locking
|
|
805
|
+
pass
|
|
806
|
+
# If no locking mechanism available, proceed without lock
|
|
807
|
+
return f
|
|
808
|
+
except Exception as e:
|
|
809
|
+
whatap_print(e)
|
|
810
|
+
try:
|
|
811
|
+
f.close()
|
|
812
|
+
except Exception as e:
|
|
813
|
+
whatap_print('WHATAP: file close failed: %s' % e)
|
|
814
|
+
return None
|
|
815
|
+
|
|
816
|
+
def get_port_number(port=6600, home=os.environ.get('WHATAP_HOME'), lock_file=None):
|
|
817
|
+
if not home:
|
|
818
|
+
return None
|
|
819
|
+
|
|
820
|
+
for i in range(100):
|
|
821
|
+
f = openPortFile(filepath=lock_file) if lock_file else openPortFile()
|
|
822
|
+
if not f:
|
|
823
|
+
if i > 50:
|
|
824
|
+
time.sleep(0.1)
|
|
825
|
+
continue
|
|
826
|
+
if f:
|
|
827
|
+
lastPortFound = None
|
|
828
|
+
for l in f.readlines():
|
|
829
|
+
l = l.strip()
|
|
830
|
+
try:
|
|
831
|
+
(portFound, portHome) = l.split()
|
|
832
|
+
portFound = int(portFound)
|
|
833
|
+
except Exception as e:
|
|
834
|
+
whatap_print('WHATAP: port file parse failed: %s' % e)
|
|
835
|
+
continue
|
|
836
|
+
if home == portHome:
|
|
837
|
+
return portFound
|
|
838
|
+
if not lastPortFound or lastPortFound < portFound:
|
|
839
|
+
lastPortFound = int(portFound)
|
|
840
|
+
if not lastPortFound:
|
|
841
|
+
lastPortFound = port
|
|
842
|
+
else:
|
|
843
|
+
lastPortFound += 1
|
|
844
|
+
f.write(str(lastPortFound))
|
|
845
|
+
f.write('\t')
|
|
846
|
+
f.write(home)
|
|
847
|
+
f.write('\n')
|
|
848
|
+
try:
|
|
849
|
+
if HAS_FCNTL:
|
|
850
|
+
fcntl.lockf(f, fcntl.LOCK_UN)
|
|
851
|
+
elif HAS_MSVCRT:
|
|
852
|
+
# Windows file unlocking using msvcrt
|
|
853
|
+
try:
|
|
854
|
+
msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, 1)
|
|
855
|
+
except OSError as e:
|
|
856
|
+
whatap_print('WHATAP: msvcrt file unlock failed: %s' % e)
|
|
857
|
+
except Exception as e:
|
|
858
|
+
whatap_print('WHATAP: file unlock failed: %s' % e)
|
|
859
|
+
f.close()
|
|
860
|
+
return lastPortFound
|
|
861
|
+
|
|
862
|
+
return port
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def configPort():
|
|
866
|
+
# force_net_udp_port가 설정된 경우 lock 파일 무시하고 해당 포트 강제 사용
|
|
867
|
+
force_port_str = preview_whatap_conf("force_net_udp_port")
|
|
868
|
+
if force_port_str and force_port_str not in ('false', 'False'):
|
|
869
|
+
try:
|
|
870
|
+
port = int(force_port_str)
|
|
871
|
+
update_config('WHATAP_HOME', 'net_udp_port', str(port))
|
|
872
|
+
return port
|
|
873
|
+
except ValueError:
|
|
874
|
+
whatap_print('WHATAP: force_net_udp_port value is invalid: {}'.format(force_port_str))
|
|
875
|
+
else:
|
|
876
|
+
port = get_port_number()
|
|
877
|
+
if port:
|
|
878
|
+
update_config('WHATAP_HOME', 'net_udp_port', str(port))
|
|
879
|
+
return port
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def configLlmPort():
|
|
883
|
+
# force_llm_net_udp_port가 설정된 경우 lock 파일 무시하고 해당 포트 강제 사용
|
|
884
|
+
force_port_str = preview_whatap_conf("force_llm_net_udp_port")
|
|
885
|
+
if force_port_str and force_port_str not in ('false', 'False'):
|
|
886
|
+
try:
|
|
887
|
+
port = int(force_port_str)
|
|
888
|
+
except ValueError:
|
|
889
|
+
whatap_print('WHATAP: force_llm_net_udp_port value is invalid: {}'.format(force_port_str))
|
|
890
|
+
port = None
|
|
891
|
+
else:
|
|
892
|
+
llm_lock_file = os.environ.get('WHATAP_LLM_LOCK_FILE', default_llm_lock_file)
|
|
893
|
+
port = get_port_number(port=6700, home=os.environ.get('WHATAP_HOME'), lock_file=llm_lock_file)
|
|
894
|
+
|
|
895
|
+
if port:
|
|
896
|
+
update_config('WHATAP_HOME', 'llm_net_udp_port', str(port))
|
|
897
|
+
return port
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def find_whatap_conf():
|
|
901
|
+
# 1. 현재 디렉토리 검색
|
|
902
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
903
|
+
parent_dir = os.path.dirname(script_dir)
|
|
904
|
+
conf_path = os.path.join(parent_dir, 'whatap.conf')
|
|
905
|
+
if os.path.exists(conf_path):
|
|
906
|
+
os.environ['WHATAP_HOME'] = parent_dir
|
|
907
|
+
return conf_path
|
|
908
|
+
|
|
909
|
+
# 2. 상위 디렉토리들 검색
|
|
910
|
+
current = parent_dir
|
|
911
|
+
# Cross-platform root detection: keep going until dirname doesn't change
|
|
912
|
+
while True:
|
|
913
|
+
conf_path = os.path.join(current, 'whatap.conf')
|
|
914
|
+
if os.path.exists(conf_path):
|
|
915
|
+
os.environ['WHATAP_HOME'] = current
|
|
916
|
+
return conf_path
|
|
917
|
+
parent = os.path.dirname(current)
|
|
918
|
+
# Stop at root (Unix: '/', Windows: 'C:\' or similar)
|
|
919
|
+
if parent == current:
|
|
920
|
+
break
|
|
921
|
+
current = parent
|
|
922
|
+
|
|
923
|
+
return None
|