vnai 2.0.2__py3-none-any.whl → 2.0.4__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 +265 -72
- vnai/beam/__init__.py +5 -2
- vnai/beam/metrics.py +182 -57
- vnai/beam/pulse.py +107 -29
- vnai/beam/quota.py +479 -102
- vnai/flow/__init__.py +5 -2
- vnai/flow/queue.py +131 -55
- vnai/flow/relay.py +439 -149
- vnai/scope/__init__.py +5 -2
- vnai/scope/profile.py +762 -219
- vnai/scope/promo.py +249 -55
- vnai/scope/state.py +220 -71
- {vnai-2.0.2.dist-info → vnai-2.0.4.dist-info}/METADATA +4 -5
- vnai-2.0.4.dist-info/RECORD +16 -0
- {vnai-2.0.2.dist-info → vnai-2.0.4.dist-info}/WHEEL +1 -1
- vnai-2.0.2.dist-info/RECORD +0 -16
- {vnai-2.0.2.dist-info → vnai-2.0.4.dist-info}/top_level.txt +0 -0
vnai/scope/promo.py
CHANGED
@@ -1,55 +1,170 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
_B='terminal'
|
7
|
-
_A='html'
|
1
|
+
"""
|
2
|
+
Promo module for vnai: fetches and presents promotional content periodically or on demand,
|
3
|
+
using logging instead of printing to avoid polluting stdout for AI tools.
|
4
|
+
"""
|
5
|
+
import logging
|
8
6
|
import requests
|
9
7
|
from datetime import datetime
|
10
|
-
import random
|
8
|
+
import random
|
9
|
+
import threading
|
10
|
+
import time
|
11
|
+
import urllib.parse
|
12
|
+
|
13
|
+
# Module-level logger setup
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
if not logger.hasHandlers():
|
16
|
+
# Add a simple stream handler that only outputs the message text
|
17
|
+
handler = logging.StreamHandler()
|
18
|
+
handler.setFormatter(logging.Formatter('%(message)s'))
|
19
|
+
logger.addHandler(handler)
|
20
|
+
logger.setLevel(logging.INFO)
|
21
|
+
|
11
22
|
class ContentManager:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
23
|
+
"""
|
24
|
+
Singleton manager to fetch remote or fallback promotional content and
|
25
|
+
present it in different environments (Jupyter, terminal, other).
|
26
|
+
|
27
|
+
Displays content automatically at randomized intervals via a background thread.
|
28
|
+
"""
|
29
|
+
_instance = None
|
30
|
+
_lock = threading.Lock()
|
31
|
+
|
32
|
+
def __new__(cls):
|
33
|
+
"""
|
34
|
+
Ensure only one instance of ContentManager is created (thread-safe).
|
35
|
+
"""
|
36
|
+
with cls._lock:
|
37
|
+
if cls._instance is None:
|
38
|
+
cls._instance = super(ContentManager, cls).__new__(cls)
|
39
|
+
cls._instance._initialize()
|
40
|
+
return cls._instance
|
41
|
+
|
42
|
+
def _initialize(self):
|
43
|
+
"""
|
44
|
+
Internal initializer: sets up display timing, URLs, and starts the periodic display thread.
|
45
|
+
"""
|
46
|
+
# Timestamp of last content display (epoch seconds)
|
47
|
+
self.last_display = 0
|
48
|
+
# Minimum interval between displays (24 hours)
|
49
|
+
self.display_interval = 24 * 3600
|
50
|
+
|
51
|
+
# Base endpoints for fetching remote content and linking
|
52
|
+
self.content_base_url = "https://vnstock-beam.hf.space/content-delivery"
|
53
|
+
self.target_url = "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
|
54
|
+
self.image_url = (
|
55
|
+
"https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
|
56
|
+
)
|
57
|
+
|
58
|
+
# Launch the background thread to periodically present content
|
59
|
+
self._start_periodic_display()
|
60
|
+
|
61
|
+
def _start_periodic_display(self):
|
62
|
+
"""
|
63
|
+
Launch a daemon thread that sleeps a random duration between 2–6 hours,
|
64
|
+
then checks if the display interval has elapsed and calls present_content.
|
65
|
+
"""
|
66
|
+
def periodic_display():
|
67
|
+
while True:
|
68
|
+
# Randomize sleep to avoid synchronized requests across instances
|
69
|
+
sleep_time = random.randint(2 * 3600, 6 * 3600)
|
70
|
+
time.sleep(sleep_time)
|
71
|
+
|
72
|
+
# Present content if enough time has passed since last_display
|
73
|
+
current_time = time.time()
|
74
|
+
if current_time - self.last_display >= self.display_interval:
|
75
|
+
self.present_content(context="periodic")
|
76
|
+
|
77
|
+
thread = threading.Thread(target=periodic_display, daemon=True)
|
78
|
+
thread.start()
|
79
|
+
|
80
|
+
def fetch_remote_content(self, context: str = "init", html: bool = True) -> str:
|
81
|
+
"""
|
82
|
+
Fetch promotional content from remote service with context and format flag.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
context: usage context (e.g., "init", "periodic", "loop").
|
86
|
+
html: if True, request HTML; otherwise plain text.
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
The content string on HTTP 200, or None on failure.
|
90
|
+
"""
|
91
|
+
try:
|
92
|
+
# Build query params and URL
|
93
|
+
params = {"context": context, "html": "true" if html else "false"}
|
94
|
+
url = f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
|
95
|
+
|
96
|
+
response = requests.get(url, timeout=3)
|
97
|
+
if response.status_code == 200:
|
98
|
+
return response.text
|
99
|
+
# Log non-200 responses at debug level
|
100
|
+
logger.debug(f"Non-200 response fetching content: {response.status_code}")
|
101
|
+
return None
|
102
|
+
except Exception as e:
|
103
|
+
# Log exceptions without interrupting user code
|
104
|
+
logger.debug(f"Failed to fetch remote content: {e}")
|
105
|
+
return None
|
106
|
+
|
107
|
+
def present_content(self, environment: str = None, context: str = "init") -> None:
|
108
|
+
"""
|
109
|
+
Present content according to the detected environment:
|
110
|
+
- In Jupyter: use display(HTML or Markdown).
|
111
|
+
- In terminal or other: log via logger.info().
|
112
|
+
|
113
|
+
Args:
|
114
|
+
environment: override detected environment ("jupyter", "terminal", else).
|
115
|
+
context: same context flag passed to fetch_remote_content and fallback logic.
|
116
|
+
"""
|
117
|
+
# Update last display timestamp
|
118
|
+
self.last_display = time.time()
|
119
|
+
|
120
|
+
# Auto-detect environment if not provided
|
121
|
+
if environment is None:
|
122
|
+
try:
|
123
|
+
from vnai.scope.profile import inspector
|
124
|
+
environment = inspector.examine().get("environment", "unknown")
|
125
|
+
except Exception:
|
126
|
+
environment = "unknown"
|
127
|
+
|
128
|
+
# Retrieve remote or HTML/text content based on environment
|
129
|
+
remote_content = self.fetch_remote_content(
|
130
|
+
context=context, html=(environment == "jupyter")
|
131
|
+
)
|
132
|
+
# Generate fallback messages if remote fetch fails
|
133
|
+
fallback = self._generate_fallback_content(context)
|
134
|
+
|
135
|
+
if environment == "jupyter":
|
136
|
+
# Rich display in Jupyter notebooks
|
137
|
+
try:
|
138
|
+
from IPython.display import display, HTML, Markdown
|
139
|
+
|
140
|
+
if remote_content:
|
141
|
+
display(HTML(remote_content))
|
142
|
+
else:
|
143
|
+
# Try Markdown, fallback to HTML
|
144
|
+
try:
|
145
|
+
display(Markdown(fallback["markdown"]))
|
146
|
+
except Exception:
|
147
|
+
display(HTML(fallback["html"]))
|
148
|
+
except Exception as e:
|
149
|
+
logger.debug(f"Jupyter display failed: {e}")
|
150
|
+
|
151
|
+
elif environment == "terminal":
|
152
|
+
# Log terminal-friendly or raw content via logger
|
153
|
+
if remote_content:
|
154
|
+
logger.info(remote_content)
|
155
|
+
else:
|
156
|
+
logger.info(fallback["terminal"])
|
157
|
+
|
158
|
+
else:
|
159
|
+
# Generic simple message for other environments
|
160
|
+
logger.info(fallback["simple"])
|
161
|
+
|
162
|
+
def _generate_fallback_content(self, context):
|
163
|
+
fallback = {"html": "", "markdown": "", "terminal": "", "simple": ""}
|
164
|
+
|
165
|
+
if context == "loop":
|
166
|
+
fallback["html"] = (
|
167
|
+
f"""
|
53
168
|
<div style="border: 1px solid #e74c3c; padding: 15px; border-radius: 5px; margin: 10px 0;">
|
54
169
|
<h3 style="color: #e74c3c;">⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests</h3>
|
55
170
|
<p>Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:</p>
|
@@ -59,8 +174,40 @@ class ContentManager:
|
|
59
174
|
<li>Tham gia gói tài trợ <a href="https://vnstocks.com/insiders-program" style="color: #3498db;">Vnstock Insider</a> để tăng 5X giới hạn API</li>
|
60
175
|
</ul>
|
61
176
|
</div>
|
62
|
-
|
63
|
-
|
177
|
+
"""
|
178
|
+
)
|
179
|
+
fallback["markdown"] = (
|
180
|
+
"""
|
181
|
+
## ⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests
|
182
|
+
|
183
|
+
Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:
|
184
|
+
* Thêm thời gian chờ giữa các lần gọi API
|
185
|
+
* Sử dụng xử lý theo batch thay vì lặp liên tục
|
186
|
+
* Tham gia gói tài trợ [Vnstock Insider](https://vnstocks.com/insiders-program) để tăng 5X giới hạn API
|
187
|
+
"""
|
188
|
+
)
|
189
|
+
fallback["terminal"] = (
|
190
|
+
"""
|
191
|
+
╔═════════════════════════════════════════════════════════════════╗
|
192
|
+
║ ║
|
193
|
+
║ 🚫 ĐANG BỊ CHẶN BỞI GIỚI HẠN API? GIẢI PHÁP Ở ĐÂY! ║
|
194
|
+
║ ║
|
195
|
+
║ ✓ Tăng ngay 500% tốc độ gọi API - Không còn lỗi RateLimit ║
|
196
|
+
║ ✓ Tiết kiệm 85% thời gian chờ đợi giữa các request ║
|
197
|
+
║ ║
|
198
|
+
║ ➤ NÂNG CẤP NGAY VỚI GÓI TÀI TRỢ VNSTOCK: ║
|
199
|
+
║ https://vnstocks.com/insiders-program ║
|
200
|
+
║ ║
|
201
|
+
╚═════════════════════════════════════════════════════════════════╝
|
202
|
+
"""
|
203
|
+
)
|
204
|
+
fallback["simple"] = (
|
205
|
+
"🚫 Đang bị giới hạn API? Tăng tốc độ gọi API lên 500% với gói "
|
206
|
+
"Vnstock Insider: https://vnstocks.com/insiders-program"
|
207
|
+
)
|
208
|
+
else:
|
209
|
+
fallback["html"] = (
|
210
|
+
f"""
|
64
211
|
<div style="border: 1px solid #3498db; padding: 15px; border-radius: 5px; margin: 10px 0;">
|
65
212
|
<h3 style="color: #3498db;">👋 Chào mừng bạn đến với Vnstock!</h3>
|
66
213
|
<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>
|
@@ -70,7 +217,54 @@ class ContentManager:
|
|
70
217
|
</ul>
|
71
218
|
<p>Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.</p>
|
72
219
|
</div>
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
220
|
+
"""
|
221
|
+
)
|
222
|
+
fallback["markdown"] = (
|
223
|
+
"""
|
224
|
+
## 👋 Chào mừng bạn đến với Vnstock!
|
225
|
+
|
226
|
+
Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
|
227
|
+
|
228
|
+
* Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn)
|
229
|
+
* Cộng đồng: [Nhóm Facebook](https://www.facebook.com/groups/vnstock.official)
|
230
|
+
|
231
|
+
Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
|
232
|
+
"""
|
233
|
+
)
|
234
|
+
fallback["terminal"] = (
|
235
|
+
"""
|
236
|
+
╔══════════════════════════════════════════════════════════╗
|
237
|
+
║ ║
|
238
|
+
║ 👋 Chào mừng bạn đến với Vnstock! ║
|
239
|
+
║ ║
|
240
|
+
║ Cảm ơn bạn đã sử dụng package phân tích ║
|
241
|
+
║ chứng khoán #1 tại Việt Nam ║
|
242
|
+
║ ║
|
243
|
+
║ ✓ Tài liệu: https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn ║
|
244
|
+
║ ✓ Cộng đồng: https://www.facebook.com/groups/vnstock.official ║
|
245
|
+
║ ║
|
246
|
+
║ Khám phá các tính năng mới nhất và tham gia ║
|
247
|
+
║ cộng đồng để nhận hỗ trợ. ║
|
248
|
+
║ ║
|
249
|
+
╚══════════════════════════════════════════════════════════╝
|
250
|
+
"""
|
251
|
+
)
|
252
|
+
fallback["simple"] = (
|
253
|
+
"👋 Chào mừng bạn đến với Vnstock! "
|
254
|
+
"Tài liệu: https://vnstocks.com/docs/tai-lieu/huong-dan-nhanh | "
|
255
|
+
"Cộng đồng: https://www.facebook.com/groups/vnstock.official"
|
256
|
+
)
|
257
|
+
return fallback
|
258
|
+
|
259
|
+
# Singleton instance for module-level use
|
260
|
+
manager = ContentManager()
|
261
|
+
|
262
|
+
|
263
|
+
def present(context: str = "init") -> None:
|
264
|
+
"""
|
265
|
+
Shortcut to ContentManager.present_content for external callers.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
context: propagate context string to ContentManager.
|
269
|
+
"""
|
270
|
+
return manager.present_content(context=context)
|
vnai/scope/state.py
CHANGED
@@ -1,74 +1,223 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
_E='peak_memory'
|
9
|
-
_D='errors'
|
10
|
-
_C=True
|
11
|
-
_B=None
|
12
|
-
_A='execution_times'
|
13
|
-
import time,threading,json,os
|
1
|
+
# vnai/scope/state.py
|
2
|
+
# System state tracking
|
3
|
+
|
4
|
+
import time
|
5
|
+
import threading
|
6
|
+
import json
|
7
|
+
import os
|
14
8
|
from datetime import datetime
|
15
9
|
from pathlib import Path
|
10
|
+
|
16
11
|
class Tracker:
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
12
|
+
"""Tracks system state and performance metrics"""
|
13
|
+
|
14
|
+
_instance = None
|
15
|
+
_lock = threading.Lock()
|
16
|
+
|
17
|
+
def __new__(cls):
|
18
|
+
with cls._lock:
|
19
|
+
if cls._instance is None:
|
20
|
+
cls._instance = super(Tracker, cls).__new__(cls)
|
21
|
+
cls._instance._initialize()
|
22
|
+
return cls._instance
|
23
|
+
|
24
|
+
def _initialize(self):
|
25
|
+
"""Initialize tracker"""
|
26
|
+
self.metrics = {
|
27
|
+
"startup_time": datetime.now().isoformat(),
|
28
|
+
"function_calls": 0,
|
29
|
+
"api_requests": 0,
|
30
|
+
"errors": 0,
|
31
|
+
"warnings": 0
|
32
|
+
}
|
33
|
+
|
34
|
+
self.performance_metrics = {
|
35
|
+
"execution_times": [],
|
36
|
+
"last_error_time": None,
|
37
|
+
"peak_memory": 0
|
38
|
+
}
|
39
|
+
|
40
|
+
self.privacy_level = "standard"
|
41
|
+
|
42
|
+
# Setup data directory
|
43
|
+
self.home_dir = Path.home()
|
44
|
+
self.project_dir = self.home_dir / ".vnstock"
|
45
|
+
self.project_dir.mkdir(exist_ok=True)
|
46
|
+
self.data_dir = self.project_dir / 'data'
|
47
|
+
self.data_dir.mkdir(exist_ok=True)
|
48
|
+
self.metrics_path = self.data_dir / "usage_metrics.json"
|
49
|
+
self.privacy_config_path = self.project_dir / 'config' / "privacy.json"
|
50
|
+
|
51
|
+
# Create config directory if it doesn't exist
|
52
|
+
os.makedirs(os.path.dirname(self.privacy_config_path), exist_ok=True)
|
53
|
+
|
54
|
+
# Load existing metrics
|
55
|
+
self._load_metrics()
|
56
|
+
|
57
|
+
# Load privacy settings
|
58
|
+
self._load_privacy_settings()
|
59
|
+
|
60
|
+
# Start background metrics collector
|
61
|
+
self._start_background_collector()
|
62
|
+
|
63
|
+
def _load_metrics(self):
|
64
|
+
"""Load metrics from file"""
|
65
|
+
if self.metrics_path.exists():
|
66
|
+
try:
|
67
|
+
with open(self.metrics_path, 'r') as f:
|
68
|
+
stored_metrics = json.load(f)
|
69
|
+
|
70
|
+
# Update metrics with stored values
|
71
|
+
for key, value in stored_metrics.items():
|
72
|
+
if key in self.metrics:
|
73
|
+
self.metrics[key] = value
|
74
|
+
except:
|
75
|
+
pass
|
76
|
+
|
77
|
+
def _save_metrics(self):
|
78
|
+
"""Save metrics to file"""
|
79
|
+
try:
|
80
|
+
with open(self.metrics_path, 'w') as f:
|
81
|
+
json.dump(self.metrics, f)
|
82
|
+
except:
|
83
|
+
pass
|
84
|
+
|
85
|
+
def _load_privacy_settings(self):
|
86
|
+
"""Load privacy settings"""
|
87
|
+
if self.privacy_config_path.exists():
|
88
|
+
try:
|
89
|
+
with open(self.privacy_config_path, 'r') as f:
|
90
|
+
settings = json.load(f)
|
91
|
+
self.privacy_level = settings.get("level", "standard")
|
92
|
+
except:
|
93
|
+
pass
|
94
|
+
|
95
|
+
def setup_privacy(self, level=None):
|
96
|
+
"""Configure privacy level for data collection"""
|
97
|
+
privacy_levels = {
|
98
|
+
"minimal": "Essential system data only",
|
99
|
+
"standard": "Performance metrics and errors",
|
100
|
+
"enhanced": "Detailed operation analytics"
|
101
|
+
}
|
102
|
+
|
103
|
+
if level is None:
|
104
|
+
# Default level
|
105
|
+
level = "standard"
|
106
|
+
|
107
|
+
if level not in privacy_levels:
|
108
|
+
raise ValueError(f"Invalid privacy level: {level}. Choose from {', '.join(privacy_levels.keys())}")
|
109
|
+
|
110
|
+
# Store preference
|
111
|
+
self.privacy_level = level
|
112
|
+
|
113
|
+
# Store in configuration file
|
114
|
+
with open(self.privacy_config_path, "w") as f:
|
115
|
+
json.dump({"level": level}, f)
|
116
|
+
|
117
|
+
return level
|
118
|
+
|
119
|
+
def get_privacy_level(self):
|
120
|
+
"""Get current privacy level"""
|
121
|
+
return self.privacy_level
|
122
|
+
|
123
|
+
def _start_background_collector(self):
|
124
|
+
"""Start background metrics collection"""
|
125
|
+
def collect_metrics():
|
126
|
+
while True:
|
127
|
+
try:
|
128
|
+
import psutil
|
129
|
+
|
130
|
+
# Update peak memory
|
131
|
+
current_process = psutil.Process()
|
132
|
+
memory_info = current_process.memory_info()
|
133
|
+
memory_usage = memory_info.rss / (1024 * 1024) # MB
|
134
|
+
|
135
|
+
if memory_usage > self.performance_metrics["peak_memory"]:
|
136
|
+
self.performance_metrics["peak_memory"] = memory_usage
|
137
|
+
|
138
|
+
# Save metrics periodically
|
139
|
+
self._save_metrics()
|
140
|
+
|
141
|
+
except:
|
142
|
+
pass
|
143
|
+
|
144
|
+
time.sleep(300) # Run every 5 minutes
|
145
|
+
|
146
|
+
# Start thread
|
147
|
+
thread = threading.Thread(target=collect_metrics, daemon=True)
|
148
|
+
thread.start()
|
149
|
+
|
150
|
+
def record(self, event_type, data=None):
|
151
|
+
"""Record an event"""
|
152
|
+
# Check privacy level
|
153
|
+
if self.privacy_level == "minimal" and event_type != "errors":
|
154
|
+
# In minimal mode, only track errors
|
155
|
+
return True
|
156
|
+
|
157
|
+
# Update counts
|
158
|
+
if event_type in self.metrics:
|
159
|
+
self.metrics[event_type] += 1
|
160
|
+
else:
|
161
|
+
self.metrics[event_type] = 1
|
162
|
+
|
163
|
+
# Special handling for errors
|
164
|
+
if event_type == "errors":
|
165
|
+
self.performance_metrics["last_error_time"] = datetime.now().isoformat()
|
166
|
+
|
167
|
+
# Special handling for function calls with timing data
|
168
|
+
if event_type == "function_calls" and data and "execution_time" in data:
|
169
|
+
# Keep up to 100 latest execution times
|
170
|
+
self.performance_metrics["execution_times"].append(data["execution_time"])
|
171
|
+
if len(self.performance_metrics["execution_times"]) > 100:
|
172
|
+
self.performance_metrics["execution_times"] = self.performance_metrics["execution_times"][-100:]
|
173
|
+
|
174
|
+
# Save if metrics change significantly
|
175
|
+
if self.metrics["function_calls"] % 100 == 0 or event_type == "errors":
|
176
|
+
self._save_metrics()
|
177
|
+
|
178
|
+
return True
|
179
|
+
|
180
|
+
def get_metrics(self):
|
181
|
+
"""Get current metrics"""
|
182
|
+
# Calculate derived metrics
|
183
|
+
avg_execution_time = 0
|
184
|
+
if self.performance_metrics["execution_times"]:
|
185
|
+
avg_execution_time = sum(self.performance_metrics["execution_times"]) / len(self.performance_metrics["execution_times"])
|
186
|
+
|
187
|
+
# Add derived metrics to output
|
188
|
+
output = self.metrics.copy()
|
189
|
+
output.update({
|
190
|
+
"avg_execution_time": avg_execution_time,
|
191
|
+
"peak_memory_mb": self.performance_metrics["peak_memory"],
|
192
|
+
"uptime": (datetime.now() - datetime.fromisoformat(self.metrics["startup_time"])).total_seconds(),
|
193
|
+
"privacy_level": self.privacy_level
|
194
|
+
})
|
195
|
+
|
196
|
+
return output
|
197
|
+
|
198
|
+
def reset(self):
|
199
|
+
"""Reset metrics"""
|
200
|
+
self.metrics = {
|
201
|
+
"startup_time": datetime.now().isoformat(),
|
202
|
+
"function_calls": 0,
|
203
|
+
"api_requests": 0,
|
204
|
+
"errors": 0,
|
205
|
+
"warnings": 0
|
206
|
+
}
|
207
|
+
|
208
|
+
self.performance_metrics = {
|
209
|
+
"execution_times": [],
|
210
|
+
"last_error_time": None,
|
211
|
+
"peak_memory": 0
|
212
|
+
}
|
213
|
+
|
214
|
+
self._save_metrics()
|
215
|
+
return True
|
216
|
+
|
217
|
+
# Create singleton instance
|
218
|
+
tracker = Tracker()
|
219
|
+
|
220
|
+
|
221
|
+
def record(event_type, data=None):
|
222
|
+
"""Record an event"""
|
223
|
+
return tracker.record(event_type, data)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: vnai
|
3
|
-
Version: 2.0.
|
3
|
+
Version: 2.0.4
|
4
4
|
Summary: System optimization and resource management toolkit
|
5
|
-
Home-page: https://github.com/
|
6
|
-
Author:
|
7
|
-
Author-email:
|
5
|
+
Home-page: https://github.com/vnstock-hq/initialization/new/main
|
6
|
+
Author: Vnstock HQ
|
7
|
+
Author-email: support@vnstocks.com
|
8
8
|
License: MIT
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
10
10
|
Classifier: Operating System :: OS Independent
|
@@ -17,7 +17,6 @@ Requires-Dist: requests>=2.25.0
|
|
17
17
|
Requires-Dist: psutil>=5.8.0
|
18
18
|
Provides-Extra: dev
|
19
19
|
Requires-Dist: pytest>=6.0.0; extra == "dev"
|
20
|
-
Requires-Dist: black>=21.5b2; extra == "dev"
|
21
20
|
Dynamic: author
|
22
21
|
Dynamic: author-email
|
23
22
|
Dynamic: classifier
|
@@ -0,0 +1,16 @@
|
|
1
|
+
vnai/__init__.py,sha256=ClnS_1T23BjbzejrIW4KTsAVBhXuq9bjDUcRP5AIcPo,9082
|
2
|
+
vnai/beam/__init__.py,sha256=xKb_iu9aAPXCulI7dENrvqVIhelSD1mIqKE9Go3GAHw,200
|
3
|
+
vnai/beam/metrics.py,sha256=xVmVw93yhKeWzRZJurmrD9mWur16HyLJl_p1XqMwW_w,7187
|
4
|
+
vnai/beam/pulse.py,sha256=jp1YwjLaMhne2nYhM5PofveDsdrSp2YtewQ2jjE78Is,3470
|
5
|
+
vnai/beam/quota.py,sha256=yP5_Z62QJwOoCEgqqWuNkzm1Dar70UiJsu6W27LNqiw,21218
|
6
|
+
vnai/flow/__init__.py,sha256=K3OeabzAWGrdPgTAOlDqrJh2y9aQW2pgLZg8tblN3ho,147
|
7
|
+
vnai/flow/queue.py,sha256=b9YKUbiXDZRC3fVgEnA77EO0EMXAi8eCoBkHnAUI5Sc,4162
|
8
|
+
vnai/flow/relay.py,sha256=RtIPRZ3BlQd-XgTbisJg0iC1HqikAjHGyyo8aTj_fUw,15766
|
9
|
+
vnai/scope/__init__.py,sha256=overJZ_UiEfBRNcSieE1GPU_9X3oS4C5l6JeBaFFVxk,267
|
10
|
+
vnai/scope/profile.py,sha256=6LL7Djke9F1HVA9eEExud2jZ5yGUfy9_NYt68nIj2-8,30737
|
11
|
+
vnai/scope/promo.py,sha256=N4aWZdh92rVNovjCwuUjv-QdhoRwloPAn4Hydx4IcRs,12515
|
12
|
+
vnai/scope/state.py,sha256=LlcZNKBy2mcAnD765BO2Tlv3Zzbak2TOEz4RUPMCFZ8,7490
|
13
|
+
vnai-2.0.4.dist-info/METADATA,sha256=rgGwwXBeevv4rjzJ-hLADRkGnPcuTaC3rMnSgd_WMRw,988
|
14
|
+
vnai-2.0.4.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
15
|
+
vnai-2.0.4.dist-info/top_level.txt,sha256=4zI0qZHePCwvgSqXl4420sBcd0VzZn4MEcRsAIFae3k,5
|
16
|
+
vnai-2.0.4.dist-info/RECORD,,
|