vnai 2.1.8__py3-none-any.whl → 2.1.9__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.
- vnai/__init__.py +37 -110
- vnai/beam/__init__.py +0 -2
- vnai/beam/metrics.py +48 -99
- vnai/beam/pulse.py +24 -53
- vnai/beam/quota.py +94 -247
- vnai/flow/__init__.py +1 -4
- vnai/flow/queue.py +17 -50
- vnai/flow/relay.py +98 -204
- vnai/scope/__init__.py +1 -4
- vnai/scope/profile.py +231 -417
- vnai/scope/promo.py +41 -123
- vnai/scope/state.py +52 -119
- {vnai-2.1.8.dist-info → vnai-2.1.9.dist-info}/METADATA +1 -1
- vnai-2.1.9.dist-info/RECORD +16 -0
- vnai-2.1.8.dist-info/RECORD +0 -16
- {vnai-2.1.8.dist-info → vnai-2.1.9.dist-info}/WHEEL +0 -0
- {vnai-2.1.8.dist-info → vnai-2.1.9.dist-info}/top_level.txt +0 -0
vnai/scope/promo.py
CHANGED
@@ -5,7 +5,6 @@ import random
|
|
5
5
|
import threading
|
6
6
|
import time
|
7
7
|
import urllib.parse
|
8
|
-
|
9
8
|
_vnii_check_attempted = False
|
10
9
|
|
11
10
|
class AdCategory:
|
@@ -20,37 +19,22 @@ class AdCategory:
|
|
20
19
|
SECURITY = 8
|
21
20
|
MAINTENANCE = 9
|
22
21
|
WARNING = 10
|
23
|
-
|
24
|
-
# Thêm import kiểm tra license từ vnii
|
25
22
|
try:
|
26
23
|
from vnii import lc_init
|
27
24
|
except ImportError:
|
28
|
-
lc_init = None
|
29
|
-
|
30
|
-
# Module-level logger setup
|
25
|
+
lc_init = None
|
31
26
|
logger = logging.getLogger(__name__)
|
32
27
|
if not logger.hasHandlers():
|
33
|
-
# Add a simple stream handler that only outputs the message text
|
34
28
|
handler = logging.StreamHandler()
|
35
29
|
handler.setFormatter(logging.Formatter('%(message)s'))
|
36
30
|
logger.addHandler(handler)
|
37
|
-
# Set to ERROR level to minimize output
|
38
31
|
logger.setLevel(logging.ERROR)
|
39
32
|
|
40
33
|
class ContentManager:
|
41
|
-
"""
|
42
|
-
Singleton manager to fetch remote or fallback promotional content and
|
43
|
-
present it in different environments (Jupyter, terminal, other).
|
44
|
-
|
45
|
-
Displays content automatically at randomized intervals via a background thread.
|
46
|
-
"""
|
47
34
|
_instance = None
|
48
35
|
_lock = threading.Lock()
|
49
36
|
|
50
37
|
def __new__(cls):
|
51
|
-
"""
|
52
|
-
Ensure only one instance of ContentManager is created (thread-safe).
|
53
|
-
"""
|
54
38
|
with cls._lock:
|
55
39
|
if cls._instance is None:
|
56
40
|
cls._instance = super(ContentManager, cls).__new__(cls)
|
@@ -58,37 +42,30 @@ class ContentManager:
|
|
58
42
|
return cls._instance
|
59
43
|
|
60
44
|
def _initialize(self, debug=False):
|
61
|
-
""
|
62
|
-
|
63
|
-
"""
|
64
|
-
# Set up content base URL
|
65
|
-
self.content_base_url = "https://hq.vnstocks.com/content-delivery"
|
66
|
-
self.is_paid_user = None # Mặc định: chưa xác định
|
45
|
+
self.content_base_url ="https://hq.vnstocks.com/content-delivery"
|
46
|
+
self.is_paid_user = None
|
67
47
|
self.license_checked = False
|
68
48
|
self._debug = debug
|
69
49
|
global _vnii_check_attempted
|
70
50
|
if _vnii_check_attempted:
|
71
|
-
# Đã kiểm tra/cài đặt vnii trước đó, không làm lại nữa
|
72
51
|
return
|
73
52
|
_vnii_check_attempted = True
|
74
|
-
# Nếu máy đã từng cài vnii, luôn cài lại bản mới nhất; nếu chưa từng cài thì không xác định được trạng thái license
|
75
53
|
import sys
|
76
54
|
import importlib
|
77
55
|
try:
|
78
56
|
import importlib.metadata
|
79
|
-
VNII_LATEST_VERSION =
|
80
|
-
VNII_URL =
|
57
|
+
VNII_LATEST_VERSION ="0.1.1"
|
58
|
+
VNII_URL =f"https://github.com/vnstock-hq/licensing/releases/download/vnii-{VNII_LATEST_VERSION}/vnii-{VNII_LATEST_VERSION}.tar.gz"
|
81
59
|
import subprocess
|
82
60
|
try:
|
83
61
|
old_version = importlib.metadata.version("vnii")
|
84
|
-
# Chỉ cài nếu version chưa đúng
|
85
62
|
if old_version != VNII_LATEST_VERSION:
|
86
63
|
try:
|
87
64
|
subprocess.check_call([
|
88
|
-
sys.executable,
|
65
|
+
sys.executable,"-m","pip","install",f"vnii@{VNII_URL}","--quiet"
|
89
66
|
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
90
67
|
importlib.invalidate_caches()
|
91
|
-
if
|
68
|
+
if"vnii" in sys.modules:
|
92
69
|
importlib.reload(sys.modules["vnii"])
|
93
70
|
else:
|
94
71
|
import vnii
|
@@ -96,17 +73,15 @@ class ContentManager:
|
|
96
73
|
except Exception as e:
|
97
74
|
logger.error(f"Lỗi khi cài đặt vnii: {e}")
|
98
75
|
pass
|
99
|
-
# Nếu đã đúng version thì không log gì cả
|
100
76
|
except importlib.metadata.PackageNotFoundError:
|
101
|
-
# Nếu chưa từng cài, không cài, luôn coi là free user
|
102
77
|
self.is_paid_user = False
|
103
78
|
return
|
104
79
|
except Exception as e:
|
105
80
|
logger.error(f"Lỗi khi kiểm tra/cài đặt vnii: {e}")
|
106
81
|
user_msg = (
|
107
|
-
|
108
|
-
|
109
|
-
|
82
|
+
"Không thể tự động cài đặt/cập nhật vnii. "
|
83
|
+
"Vui lòng liên hệ admin hoặc hỗ trợ kỹ thuật của Vnstock để được trợ giúp. "
|
84
|
+
f"Chi tiết lỗi: {e}"
|
110
85
|
)
|
111
86
|
logger.error(user_msg)
|
112
87
|
try:
|
@@ -115,15 +90,13 @@ class ContentManager:
|
|
115
90
|
pass
|
116
91
|
self.is_paid_user = False
|
117
92
|
return
|
118
|
-
|
119
|
-
# Kiểm tra trạng thái paid user (sponsor) và cache lại
|
120
93
|
if lc_init is not None:
|
121
94
|
try:
|
122
95
|
license_info = lc_init(repo_name='vnstock', debug=self._debug)
|
123
|
-
status = license_info.get('status',
|
96
|
+
status = license_info.get('status','').lower()
|
124
97
|
if self._debug:
|
125
98
|
logger.info(f"License check result: {status}")
|
126
|
-
if
|
99
|
+
if'recognized and verified' in status:
|
127
100
|
self.is_paid_user = True
|
128
101
|
if self._debug:
|
129
102
|
logger.info("Detected paid user (license recognized and verified).")
|
@@ -140,110 +113,72 @@ class ContentManager:
|
|
140
113
|
if self._debug:
|
141
114
|
logger.warning("Không tìm thấy package vnii. Không xác định được trạng thái paid user.")
|
142
115
|
self.is_paid_user = None
|
143
|
-
|
144
|
-
# Timestamp of last content display (epoch seconds)
|
145
116
|
self.last_display = 0
|
146
|
-
# Minimum interval between displays (24 hours)
|
147
117
|
self.display_interval = 24 * 3600
|
148
|
-
|
149
|
-
|
150
|
-
self.content_base_url = "https://hq.vnstocks.com/content-delivery"
|
151
|
-
self.target_url = "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
|
118
|
+
self.content_base_url ="https://hq.vnstocks.com/content-delivery"
|
119
|
+
self.target_url ="https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
|
152
120
|
self.image_url = (
|
153
|
-
|
121
|
+
"https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
|
154
122
|
)
|
155
|
-
|
156
|
-
# Launch the background thread to periodically present content
|
157
123
|
self._start_periodic_display()
|
158
124
|
|
159
125
|
def _start_periodic_display(self):
|
160
|
-
"""
|
161
|
-
Launch a daemon thread that sleeps a random duration between 2–6 hours,
|
162
|
-
then checks if the display interval has elapsed and calls present_content.
|
163
|
-
"""
|
164
126
|
def periodic_display():
|
165
127
|
while True:
|
166
|
-
# Nếu là paid user thì không bao giờ hiện ads
|
167
128
|
if self.is_paid_user:
|
168
129
|
break
|
169
|
-
# Randomize sleep to avoid synchronized requests across instances
|
170
130
|
sleep_time = random.randint(2 * 3600, 6 * 3600)
|
171
131
|
time.sleep(sleep_time)
|
172
|
-
|
173
|
-
# Present content if enough time has passed since last_display
|
174
132
|
current_time = time.time()
|
175
133
|
if current_time - self.last_display >= self.display_interval:
|
176
134
|
self.present_content(context="periodic")
|
177
135
|
else:
|
178
136
|
pass
|
179
|
-
|
180
137
|
thread = threading.Thread(target=periodic_display, daemon=True)
|
181
138
|
thread.start()
|
182
139
|
|
183
|
-
def fetch_remote_content(self, context: str =
|
140
|
+
def fetch_remote_content(self, context: str ="init", html: bool = True) -> str:
|
184
141
|
if self.is_paid_user:
|
185
|
-
return
|
186
|
-
|
142
|
+
return""
|
187
143
|
"""
|
188
144
|
Fetch promotional content from remote service with context and format flag.
|
189
|
-
|
190
145
|
Args:
|
191
146
|
context: usage context (e.g., "init", "periodic", "loop").
|
192
147
|
html: if True, request HTML; otherwise plain text.
|
193
|
-
|
194
148
|
Returns:
|
195
149
|
The content string on HTTP 200, or None on failure.
|
196
150
|
"""
|
197
151
|
try:
|
198
|
-
|
199
|
-
|
200
|
-
url = f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
|
201
|
-
|
152
|
+
params = {"context": context,"html":"true" if html else"false"}
|
153
|
+
url =f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
|
202
154
|
response = requests.get(url, timeout=3)
|
203
155
|
if response.status_code == 200:
|
204
156
|
return response.text
|
205
|
-
# Log non-200 responses at debug level
|
206
157
|
logger.error(f"Non-200 response fetching content: {response.status_code}")
|
207
158
|
return None
|
208
159
|
except Exception as e:
|
209
|
-
# Log exceptions without interrupting user code
|
210
160
|
logger.error(f"Failed to fetch remote content: {e}")
|
211
161
|
return None
|
212
162
|
|
213
|
-
def present_content(self, context: str =
|
163
|
+
def present_content(self, context: str ="init", ad_category: int = AdCategory.FREE) -> None:
|
214
164
|
environment = None
|
215
|
-
|
216
|
-
Display promotional content in the appropriate environment.
|
217
|
-
ad_category: Loại quảng cáo (FREE, ANNOUNCEMENT, ...)
|
218
|
-
"""
|
219
|
-
# Nếu là paid user và ad_category là FREE thì skip, còn lại vẫn hiện
|
220
|
-
if getattr(self, 'is_paid_user', False) and ad_category == AdCategory.FREE:
|
165
|
+
if getattr(self,'is_paid_user', False) and ad_category == AdCategory.FREE:
|
221
166
|
return
|
222
|
-
|
223
|
-
# Update last display timestamp
|
224
167
|
self.last_display = time.time()
|
225
|
-
|
226
|
-
# Auto-detect environment if not provided
|
227
168
|
if environment is None:
|
228
169
|
try:
|
229
170
|
from vnai.scope.profile import inspector
|
230
|
-
environment = inspector.examine().get("environment",
|
171
|
+
environment = inspector.examine().get("environment","unknown")
|
231
172
|
except Exception as e:
|
232
173
|
logger.error(f"Không detect được environment: {e}")
|
233
|
-
environment =
|
234
|
-
|
235
|
-
# Retrieve remote or HTML/text content based on environment
|
174
|
+
environment ="unknown"
|
236
175
|
remote_content = self.fetch_remote_content(
|
237
|
-
context=context, html=(environment ==
|
176
|
+
context=context, html=(environment =="jupyter")
|
238
177
|
)
|
239
|
-
|
240
|
-
# Generate fallback messages if remote fetch fails
|
241
178
|
fallback = self._generate_fallback_content(context)
|
242
|
-
|
243
|
-
if environment == "jupyter":
|
179
|
+
if environment =="jupyter":
|
244
180
|
try:
|
245
181
|
from IPython.display import display, HTML, Markdown
|
246
|
-
|
247
182
|
if remote_content:
|
248
183
|
display(HTML(remote_content))
|
249
184
|
else:
|
@@ -253,23 +188,19 @@ class ContentManager:
|
|
253
188
|
display(HTML(fallback["html"]))
|
254
189
|
except Exception as e:
|
255
190
|
pass
|
256
|
-
|
257
|
-
elif environment == "terminal":
|
258
|
-
# Hiển thị quảng cáo dạng text ra terminal
|
191
|
+
elif environment =="terminal":
|
259
192
|
if remote_content:
|
260
193
|
print(remote_content)
|
261
194
|
else:
|
262
195
|
print(fallback["terminal"])
|
263
|
-
|
264
196
|
else:
|
265
197
|
print(fallback["simple"])
|
266
198
|
|
267
199
|
def _generate_fallback_content(self, context):
|
268
|
-
fallback = {"html":
|
269
|
-
|
270
|
-
if context == "loop":
|
200
|
+
fallback = {"html":"","markdown":"","terminal":"","simple":""}
|
201
|
+
if context =="loop":
|
271
202
|
fallback["html"] = (
|
272
|
-
|
203
|
+
f"""
|
273
204
|
<div style="border: 1px solid #e74c3c; padding: 15px; border-radius: 5px; margin: 10px 0;">
|
274
205
|
<h3 style="color: #e74c3c;">⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests</h3>
|
275
206
|
<p>Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:</p>
|
@@ -282,9 +213,8 @@ class ContentManager:
|
|
282
213
|
"""
|
283
214
|
)
|
284
215
|
fallback["markdown"] = (
|
285
|
-
|
216
|
+
"""
|
286
217
|
## ⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests
|
287
|
-
|
288
218
|
Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:
|
289
219
|
* Thêm thời gian chờ giữa các lần gọi API
|
290
220
|
* Sử dụng xử lý theo batch thay vì lặp liên tục
|
@@ -292,7 +222,7 @@ class ContentManager:
|
|
292
222
|
"""
|
293
223
|
)
|
294
224
|
fallback["terminal"] = (
|
295
|
-
|
225
|
+
"""
|
296
226
|
╔═════════════════════════════════════════════════════════════════╗
|
297
227
|
║ ║
|
298
228
|
║ 🚫 ĐANG BỊ CHẶN BỞI GIỚI HẠN API? GIẢI PHÁP Ở ĐÂY! ║
|
@@ -307,12 +237,12 @@ class ContentManager:
|
|
307
237
|
"""
|
308
238
|
)
|
309
239
|
fallback["simple"] = (
|
310
|
-
|
311
|
-
|
240
|
+
"🚫 Đang bị giới hạn API? Tăng tốc độ gọi API lên 500% với gói "
|
241
|
+
"Vnstock Insider: https://vnstocks.com/insiders-program"
|
312
242
|
)
|
313
243
|
else:
|
314
244
|
fallback["html"] = (
|
315
|
-
|
245
|
+
f"""
|
316
246
|
<div style="border: 1px solid #3498db; padding: 15px; border-radius: 5px; margin: 10px 0;">
|
317
247
|
<h3 style="color: #3498db;">👋 Chào mừng bạn đến với Vnstock!</h3>
|
318
248
|
<p>Cảm ơn bạn đã sử dụng thư viện phân tích chứng khoán #1 tại Việt Nam cho Python</p>
|
@@ -325,19 +255,16 @@ class ContentManager:
|
|
325
255
|
"""
|
326
256
|
)
|
327
257
|
fallback["markdown"] = (
|
328
|
-
|
258
|
+
"""
|
329
259
|
## 👋 Chào mừng bạn đến với Vnstock!
|
330
|
-
|
331
260
|
Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
|
332
|
-
|
333
261
|
* Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)
|
334
262
|
* Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)
|
335
|
-
|
336
263
|
Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
|
337
264
|
"""
|
338
265
|
)
|
339
266
|
fallback["terminal"] = (
|
340
|
-
|
267
|
+
"""
|
341
268
|
╔════════════════════════════════════════════════════════════╗
|
342
269
|
║ ║
|
343
270
|
║ 👋 Chào mừng bạn đến với Vnstock! ║
|
@@ -355,21 +282,12 @@ Khám phá các tính năng mới nhất và tham gia cộng đồng để nhậ
|
|
355
282
|
"""
|
356
283
|
)
|
357
284
|
fallback["simple"] = (
|
358
|
-
|
359
|
-
|
360
|
-
|
285
|
+
"👋 Chào mừng bạn đến với Vnstock! "
|
286
|
+
"Tài liệu: https://vnstocks.com/onboard | "
|
287
|
+
"Cộng đồng: https://facebook.com/groups/vnstock.official"
|
361
288
|
)
|
362
289
|
return fallback
|
363
|
-
|
364
|
-
# Singleton instance for module-level use
|
365
290
|
manager = ContentManager()
|
366
291
|
|
367
|
-
def present(context: str =
|
368
|
-
|
369
|
-
Shortcut to ContentManager.present_content for external callers.
|
370
|
-
|
371
|
-
Args:
|
372
|
-
context: propagate context string to ContentManager.
|
373
|
-
ad_category: loại quảng cáo (FREE, ANNOUNCEMENT, ...)
|
374
|
-
"""
|
375
|
-
manager.present_content(context=context, ad_category=ad_category)
|
292
|
+
def present(context: str ="init", ad_category: int = AdCategory.FREE) -> None:
|
293
|
+
manager.present_content(context=context, ad_category=ad_category)
|
vnai/scope/state.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# vnai/scope/state.py
|
2
|
-
|
3
1
|
import time
|
4
2
|
import threading
|
5
3
|
import json
|
@@ -8,215 +6,150 @@ from datetime import datetime
|
|
8
6
|
from pathlib import Path
|
9
7
|
|
10
8
|
class Tracker:
|
11
|
-
"""Tracks system state and performance metrics"""
|
12
|
-
|
13
9
|
_instance = None
|
14
10
|
_lock = threading.Lock()
|
15
|
-
|
11
|
+
|
16
12
|
def __new__(cls):
|
17
13
|
with cls._lock:
|
18
14
|
if cls._instance is None:
|
19
15
|
cls._instance = super(Tracker, cls).__new__(cls)
|
20
16
|
cls._instance._initialize()
|
21
17
|
return cls._instance
|
22
|
-
|
18
|
+
|
23
19
|
def _initialize(self):
|
24
|
-
"""Initialize tracker"""
|
25
20
|
self.metrics = {
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
21
|
+
"startup_time": datetime.now().isoformat(),
|
22
|
+
"function_calls": 0,
|
23
|
+
"api_requests": 0,
|
24
|
+
"errors": 0,
|
25
|
+
"warnings": 0
|
31
26
|
}
|
32
|
-
|
33
27
|
self.performance_metrics = {
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
"execution_times": [],
|
29
|
+
"last_error_time": None,
|
30
|
+
"peak_memory": 0
|
37
31
|
}
|
38
|
-
|
39
|
-
self.privacy_level = "standard"
|
40
|
-
|
41
|
-
# Setup data directory
|
32
|
+
self.privacy_level ="standard"
|
42
33
|
self.home_dir = Path.home()
|
43
|
-
self.project_dir = self.home_dir /
|
34
|
+
self.project_dir = self.home_dir /".vnstock"
|
44
35
|
self.project_dir.mkdir(exist_ok=True)
|
45
|
-
self.data_dir = self.project_dir /
|
36
|
+
self.data_dir = self.project_dir /'data'
|
46
37
|
self.data_dir.mkdir(exist_ok=True)
|
47
|
-
self.metrics_path = self.data_dir /
|
48
|
-
self.privacy_config_path = self.project_dir /
|
49
|
-
|
50
|
-
# Create config directory if it doesn't exist
|
38
|
+
self.metrics_path = self.data_dir /"usage_metrics.json"
|
39
|
+
self.privacy_config_path = self.project_dir /'config' /"privacy.json"
|
51
40
|
os.makedirs(os.path.dirname(self.privacy_config_path), exist_ok=True)
|
52
|
-
|
53
|
-
# Load existing metrics
|
54
41
|
self._load_metrics()
|
55
|
-
|
56
|
-
# Load privacy settings
|
57
42
|
self._load_privacy_settings()
|
58
|
-
|
59
|
-
# Start background metrics collector
|
60
43
|
self._start_background_collector()
|
61
|
-
|
44
|
+
|
62
45
|
def _load_metrics(self):
|
63
|
-
"""Load metrics from file"""
|
64
46
|
if self.metrics_path.exists():
|
65
47
|
try:
|
66
|
-
with open(self.metrics_path,
|
48
|
+
with open(self.metrics_path,'r') as f:
|
67
49
|
stored_metrics = json.load(f)
|
68
|
-
|
69
|
-
# Update metrics with stored values
|
70
50
|
for key, value in stored_metrics.items():
|
71
51
|
if key in self.metrics:
|
72
52
|
self.metrics[key] = value
|
73
53
|
except:
|
74
54
|
pass
|
75
|
-
|
55
|
+
|
76
56
|
def _save_metrics(self):
|
77
|
-
"""Save metrics to file"""
|
78
57
|
try:
|
79
|
-
with open(self.metrics_path,
|
58
|
+
with open(self.metrics_path,'w') as f:
|
80
59
|
json.dump(self.metrics, f)
|
81
60
|
except:
|
82
61
|
pass
|
83
|
-
|
62
|
+
|
84
63
|
def _load_privacy_settings(self):
|
85
|
-
"""Load privacy settings"""
|
86
64
|
if self.privacy_config_path.exists():
|
87
65
|
try:
|
88
|
-
with open(self.privacy_config_path,
|
66
|
+
with open(self.privacy_config_path,'r') as f:
|
89
67
|
settings = json.load(f)
|
90
|
-
self.privacy_level = settings.get("level",
|
68
|
+
self.privacy_level = settings.get("level","standard")
|
91
69
|
except:
|
92
70
|
pass
|
93
|
-
|
71
|
+
|
94
72
|
def setup_privacy(self, level=None):
|
95
|
-
"""Configure privacy level for data collection"""
|
96
73
|
privacy_levels = {
|
97
|
-
|
98
|
-
|
99
|
-
|
74
|
+
"minimal":"Essential system data only",
|
75
|
+
"standard":"Performance metrics and errors",
|
76
|
+
"enhanced":"Detailed operation analytics"
|
100
77
|
}
|
101
|
-
|
102
78
|
if level is None:
|
103
|
-
|
104
|
-
level = "standard"
|
105
|
-
|
79
|
+
level ="standard"
|
106
80
|
if level not in privacy_levels:
|
107
81
|
raise ValueError(f"Invalid privacy level: {level}. Choose from {', '.join(privacy_levels.keys())}")
|
108
|
-
|
109
|
-
# Store preference
|
110
82
|
self.privacy_level = level
|
111
|
-
|
112
|
-
# Store in configuration file
|
113
|
-
with open(self.privacy_config_path, "w") as f:
|
83
|
+
with open(self.privacy_config_path,"w") as f:
|
114
84
|
json.dump({"level": level}, f)
|
115
|
-
|
116
85
|
return level
|
117
|
-
|
86
|
+
|
118
87
|
def get_privacy_level(self):
|
119
|
-
"""Get current privacy level"""
|
120
88
|
return self.privacy_level
|
121
|
-
|
89
|
+
|
122
90
|
def _start_background_collector(self):
|
123
|
-
"""Start background metrics collection"""
|
124
91
|
def collect_metrics():
|
125
92
|
while True:
|
126
93
|
try:
|
127
94
|
import psutil
|
128
|
-
|
129
|
-
# Update peak memory
|
130
95
|
current_process = psutil.Process()
|
131
96
|
memory_info = current_process.memory_info()
|
132
|
-
memory_usage = memory_info.rss / (1024 * 1024)
|
133
|
-
|
97
|
+
memory_usage = memory_info.rss / (1024 * 1024)
|
134
98
|
if memory_usage > self.performance_metrics["peak_memory"]:
|
135
99
|
self.performance_metrics["peak_memory"] = memory_usage
|
136
|
-
|
137
|
-
# Save metrics periodically
|
138
100
|
self._save_metrics()
|
139
|
-
|
140
101
|
except:
|
141
102
|
pass
|
142
|
-
|
143
|
-
time.sleep(300) # Run every 5 minutes
|
144
|
-
|
145
|
-
# Start thread
|
103
|
+
time.sleep(300)
|
146
104
|
thread = threading.Thread(target=collect_metrics, daemon=True)
|
147
105
|
thread.start()
|
148
|
-
|
106
|
+
|
149
107
|
def record(self, event_type, data=None):
|
150
|
-
""
|
151
|
-
# Check privacy level
|
152
|
-
if self.privacy_level == "minimal" and event_type != "errors":
|
153
|
-
# In minimal mode, only track errors
|
108
|
+
if self.privacy_level =="minimal" and event_type !="errors":
|
154
109
|
return True
|
155
|
-
|
156
|
-
# Update counts
|
157
110
|
if event_type in self.metrics:
|
158
111
|
self.metrics[event_type] += 1
|
159
112
|
else:
|
160
113
|
self.metrics[event_type] = 1
|
161
|
-
|
162
|
-
# Special handling for errors
|
163
|
-
if event_type == "errors":
|
114
|
+
if event_type =="errors":
|
164
115
|
self.performance_metrics["last_error_time"] = datetime.now().isoformat()
|
165
|
-
|
166
|
-
# Special handling for function calls with timing data
|
167
|
-
if event_type == "function_calls" and data and "execution_time" in data:
|
168
|
-
# Keep up to 100 latest execution times
|
116
|
+
if event_type =="function_calls" and data and"execution_time" in data:
|
169
117
|
self.performance_metrics["execution_times"].append(data["execution_time"])
|
170
118
|
if len(self.performance_metrics["execution_times"]) > 100:
|
171
119
|
self.performance_metrics["execution_times"] = self.performance_metrics["execution_times"][-100:]
|
172
|
-
|
173
|
-
# Save if metrics change significantly
|
174
|
-
if self.metrics["function_calls"] % 100 == 0 or event_type == "errors":
|
120
|
+
if self.metrics["function_calls"] % 100 == 0 or event_type =="errors":
|
175
121
|
self._save_metrics()
|
176
|
-
|
177
122
|
return True
|
178
|
-
|
123
|
+
|
179
124
|
def get_metrics(self):
|
180
|
-
"""Get current metrics"""
|
181
|
-
# Calculate derived metrics
|
182
125
|
avg_execution_time = 0
|
183
126
|
if self.performance_metrics["execution_times"]:
|
184
127
|
avg_execution_time = sum(self.performance_metrics["execution_times"]) / len(self.performance_metrics["execution_times"])
|
185
|
-
|
186
|
-
# Add derived metrics to output
|
187
128
|
output = self.metrics.copy()
|
188
129
|
output.update({
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
130
|
+
"avg_execution_time": avg_execution_time,
|
131
|
+
"peak_memory_mb": self.performance_metrics["peak_memory"],
|
132
|
+
"uptime": (datetime.now() - datetime.fromisoformat(self.metrics["startup_time"])).total_seconds(),
|
133
|
+
"privacy_level": self.privacy_level
|
193
134
|
})
|
194
|
-
|
195
135
|
return output
|
196
|
-
|
136
|
+
|
197
137
|
def reset(self):
|
198
|
-
"""Reset metrics"""
|
199
138
|
self.metrics = {
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
139
|
+
"startup_time": datetime.now().isoformat(),
|
140
|
+
"function_calls": 0,
|
141
|
+
"api_requests": 0,
|
142
|
+
"errors": 0,
|
143
|
+
"warnings": 0
|
205
144
|
}
|
206
|
-
|
207
145
|
self.performance_metrics = {
|
208
|
-
|
209
|
-
|
210
|
-
|
146
|
+
"execution_times": [],
|
147
|
+
"last_error_time": None,
|
148
|
+
"peak_memory": 0
|
211
149
|
}
|
212
|
-
|
213
150
|
self._save_metrics()
|
214
151
|
return True
|
215
|
-
|
216
|
-
# Create singleton instance
|
217
152
|
tracker = Tracker()
|
218
153
|
|
219
|
-
|
220
154
|
def record(event_type, data=None):
|
221
|
-
|
222
|
-
return tracker.record(event_type, data)
|
155
|
+
return tracker.record(event_type, data)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
vnai/__init__.py,sha256=oTfe5Hu8UUDUW3Gq84fqX4InkIfqIZTUqTv2Q28RB3c,5733
|
2
|
+
vnai/beam/__init__.py,sha256=h19ZEQFnWTvqed0mpTcuQf-sT9XifWhfVbaobTEs91Q,131
|
3
|
+
vnai/beam/metrics.py,sha256=2BmdJl7gB4rHwCN7G7WUMg_6rnlY3xfulBzU7usqNfE,6275
|
4
|
+
vnai/beam/pulse.py,sha256=xu7cStqdkCw8be_yVwfjGtqdy-stYC511oZOkT9j6hs,2485
|
5
|
+
vnai/beam/quota.py,sha256=0pgkCdARtojF-xNC0Y_VYYo4GO717F0MOexwNH2NdzY,14416
|
6
|
+
vnai/flow/__init__.py,sha256=9JepoOHS1xn0_ZrDyfft42g2cjhaka7xdG0velqmk-I,70
|
7
|
+
vnai/flow/queue.py,sha256=9dyMwxen1vCiFJ7wNZn-kzMY8DnB4xD575J1pPSWZLw,3223
|
8
|
+
vnai/flow/relay.py,sha256=7VUsiU1T272chBRCRR90Aodfh_PcUUAUlX3SPUtltFQ,12624
|
9
|
+
vnai/scope/__init__.py,sha256=gVvOMQ34V3Tm8_XmX4cRCrgKVfZ4XYQup79MWx6CWuM,197
|
10
|
+
vnai/scope/profile.py,sha256=RSk8w5h9rvamFozQOUrYcDSXssTF0ro70Qq-VfExQF0,21567
|
11
|
+
vnai/scope/promo.py,sha256=AcQDdzcIYCrTQ0Hqr0KVBCOJsBYQevp0T6emQlGlQ-E,13597
|
12
|
+
vnai/scope/state.py,sha256=ApzP6U0Hzp047-k3czL933soUXiZjpZSxXk7DKwf7yM,5389
|
13
|
+
vnai-2.1.9.dist-info/METADATA,sha256=RiB-4I-sviuL_7kRXBtEPsQyfLs15bJ_nEPHVC6qEbY,662
|
14
|
+
vnai-2.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
vnai-2.1.9.dist-info/top_level.txt,sha256=4zI0qZHePCwvgSqXl4420sBcd0VzZn4MEcRsAIFae3k,5
|
16
|
+
vnai-2.1.9.dist-info/RECORD,,
|