vnai 2.1.7__py3-none-any.whl → 2.1.8__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 +240 -178
- vnai/beam/__init__.py +4 -2
- vnai/beam/metrics.py +218 -167
- vnai/beam/pulse.py +108 -79
- vnai/beam/quota.py +486 -333
- vnai/flow/__init__.py +5 -2
- vnai/flow/queue.py +133 -100
- vnai/flow/relay.py +447 -356
- vnai/scope/__init__.py +7 -4
- vnai/scope/profile.py +765 -579
- vnai/scope/promo.py +375 -278
- vnai/scope/state.py +222 -155
- {vnai-2.1.7.dist-info → vnai-2.1.8.dist-info}/METADATA +20 -20
- vnai-2.1.8.dist-info/RECORD +16 -0
- vnai-2.1.7.dist-info/RECORD +0 -16
- {vnai-2.1.7.dist-info → vnai-2.1.8.dist-info}/WHEEL +0 -0
- {vnai-2.1.7.dist-info → vnai-2.1.8.dist-info}/top_level.txt +0 -0
vnai/beam/metrics.py
CHANGED
@@ -1,167 +1,218 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import
|
4
|
-
|
5
|
-
import
|
6
|
-
import
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
"
|
25
|
-
|
26
|
-
self.
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
self.
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
self.
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
"
|
149
|
-
"
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
"
|
159
|
-
"
|
160
|
-
"
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
1
|
+
# vnai/beam/metrics.py
|
2
|
+
|
3
|
+
import sys
|
4
|
+
import time
|
5
|
+
import threading
|
6
|
+
from datetime import datetime
|
7
|
+
import hashlib
|
8
|
+
import json
|
9
|
+
|
10
|
+
class Collector:
|
11
|
+
"""Collects operation metrics for system optimization"""
|
12
|
+
|
13
|
+
_instance = None
|
14
|
+
_lock = threading.Lock()
|
15
|
+
|
16
|
+
def __new__(cls):
|
17
|
+
with cls._lock:
|
18
|
+
if cls._instance is None:
|
19
|
+
cls._instance = super(Collector, cls).__new__(cls)
|
20
|
+
cls._instance._initialize()
|
21
|
+
return cls._instance
|
22
|
+
|
23
|
+
def _initialize(self):
|
24
|
+
"""Initialize collector"""
|
25
|
+
# Initialize metrics storage
|
26
|
+
self.metrics = {
|
27
|
+
"function": [],
|
28
|
+
"rate_limit": [],
|
29
|
+
"request": [],
|
30
|
+
"error": []
|
31
|
+
}
|
32
|
+
# Configuration thresholds
|
33
|
+
self.thresholds = {
|
34
|
+
"buffer_size": 50,
|
35
|
+
"error_threshold": 0.1,
|
36
|
+
"performance_threshold": 5.0
|
37
|
+
}
|
38
|
+
# Tracking variables
|
39
|
+
self.function_count = 0
|
40
|
+
self.colab_auth_triggered = False
|
41
|
+
self.max_metric_length = 200 # Keep only the latest 200 entries
|
42
|
+
self._last_record_time = {} # Track last record time for throttling
|
43
|
+
self.min_interval_per_type = 0.5 # Min interval between same type records
|
44
|
+
self._recent_hashes = [] # Track recent hashes for deduplication
|
45
|
+
self._sending_metrics = False # Prevent reentrancy in _send_metrics
|
46
|
+
|
47
|
+
def record(self, metric_type, data, priority=None):
|
48
|
+
"""Record operation metrics with deduplication and throttling"""
|
49
|
+
|
50
|
+
# Ensure data is a dictionary
|
51
|
+
if not isinstance(data, dict):
|
52
|
+
data = {"value": str(data)}
|
53
|
+
|
54
|
+
# Add timestamp if not present
|
55
|
+
if "timestamp" not in data:
|
56
|
+
data["timestamp"] = datetime.now().isoformat()
|
57
|
+
|
58
|
+
# For non-system info, simplify and tag machine
|
59
|
+
if metric_type != "system_info":
|
60
|
+
data.pop("system", None)
|
61
|
+
from vnai.scope.profile import inspector
|
62
|
+
data["machine_id"] = inspector.fingerprint()
|
63
|
+
|
64
|
+
# ==== THROTTLING ====
|
65
|
+
now = time.time()
|
66
|
+
last_time = self._last_record_time.get(metric_type, 0)
|
67
|
+
if now - last_time < self.min_interval_per_type and priority != "high":
|
68
|
+
return # Skip due to interval limit
|
69
|
+
self._last_record_time[metric_type] = now
|
70
|
+
|
71
|
+
# ==== DEDUPLICATION ====
|
72
|
+
data_hash = hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
|
73
|
+
if data_hash in self._recent_hashes and priority != "high":
|
74
|
+
return # Skip duplicate
|
75
|
+
self._recent_hashes.append(data_hash)
|
76
|
+
|
77
|
+
# ==== RECORD LOGIC ====
|
78
|
+
if metric_type in self.metrics:
|
79
|
+
self.metrics[metric_type].append(data)
|
80
|
+
# Prune oldest if too long
|
81
|
+
if len(self.metrics[metric_type]) > self.max_metric_length:
|
82
|
+
self.metrics[metric_type] = self.metrics[metric_type][-self.max_metric_length:]
|
83
|
+
else:
|
84
|
+
self.metrics["function"].append(data)
|
85
|
+
|
86
|
+
# Function metric tracking (Colab trigger)
|
87
|
+
if metric_type == "function":
|
88
|
+
self.function_count += 1
|
89
|
+
if self.function_count > 10 and not self.colab_auth_triggered and 'google.colab' in sys.modules:
|
90
|
+
self.colab_auth_triggered = True
|
91
|
+
threading.Thread(target=self._trigger_colab_auth, daemon=True).start()
|
92
|
+
|
93
|
+
# Auto-send triggers
|
94
|
+
if sum(len(metric_list) for metric_list in self.metrics.values()) >= self.thresholds["buffer_size"]:
|
95
|
+
self._send_metrics()
|
96
|
+
if priority == "high" or metric_type == "error":
|
97
|
+
self._send_metrics()
|
98
|
+
|
99
|
+
def _trigger_colab_auth(self):
|
100
|
+
"""Trigger Google Colab authentication in a background thread"""
|
101
|
+
try:
|
102
|
+
from vnai.scope.profile import inspector
|
103
|
+
inspector.get_or_create_user_id()
|
104
|
+
except:
|
105
|
+
pass # Silently fail if there's an issue
|
106
|
+
|
107
|
+
def _send_metrics(self):
|
108
|
+
"""Send collected metrics to data relay"""
|
109
|
+
# Prevent reentrancy
|
110
|
+
if self._sending_metrics:
|
111
|
+
return
|
112
|
+
|
113
|
+
self._sending_metrics = True
|
114
|
+
try:
|
115
|
+
# Import here to avoid circular imports
|
116
|
+
from vnai.flow.relay import track_function_call, track_rate_limit, track_api_request
|
117
|
+
except ImportError:
|
118
|
+
# If relay module is not available, clear metrics and return
|
119
|
+
for metric_type in self.metrics:
|
120
|
+
self.metrics[metric_type] = []
|
121
|
+
self._sending_metrics = False
|
122
|
+
return
|
123
|
+
|
124
|
+
# Process and send each type of metric using the appropriate tracking function
|
125
|
+
for metric_type, data_list in self.metrics.items():
|
126
|
+
if not data_list:
|
127
|
+
continue
|
128
|
+
|
129
|
+
# Process each metric by type
|
130
|
+
for data in data_list:
|
131
|
+
try:
|
132
|
+
if metric_type == "function":
|
133
|
+
# Use the track_function_call interface
|
134
|
+
track_function_call(
|
135
|
+
function_name=data.get("function", "unknown"),
|
136
|
+
source=data.get("source", "vnai"),
|
137
|
+
execution_time=data.get("execution_time", 0),
|
138
|
+
success=data.get("success", True),
|
139
|
+
error=data.get("error"),
|
140
|
+
args=data.get("args")
|
141
|
+
)
|
142
|
+
elif metric_type == "rate_limit":
|
143
|
+
# Use the track_rate_limit interface
|
144
|
+
track_rate_limit(
|
145
|
+
source=data.get("source", "vnai"),
|
146
|
+
limit_type=data.get("limit_type", "unknown"),
|
147
|
+
limit_value=data.get("limit_value", 0),
|
148
|
+
current_usage=data.get("current_usage", 0),
|
149
|
+
is_exceeded=data.get("is_exceeded", False)
|
150
|
+
)
|
151
|
+
elif metric_type == "request":
|
152
|
+
# Use the track_api_request interface
|
153
|
+
track_api_request(
|
154
|
+
endpoint=data.get("endpoint", "unknown"),
|
155
|
+
source=data.get("source", "vnai"),
|
156
|
+
method=data.get("method", "GET"),
|
157
|
+
status_code=data.get("status_code", 200),
|
158
|
+
execution_time=data.get("execution_time", 0),
|
159
|
+
request_size=data.get("request_size", 0),
|
160
|
+
response_size=data.get("response_size", 0)
|
161
|
+
)
|
162
|
+
except Exception as e:
|
163
|
+
# If tracking fails, just continue with the next item
|
164
|
+
continue
|
165
|
+
|
166
|
+
# Clear the processed metrics
|
167
|
+
self.metrics[metric_type] = []
|
168
|
+
|
169
|
+
# Reset sending flag
|
170
|
+
self._sending_metrics = False
|
171
|
+
|
172
|
+
def get_metrics_summary(self):
|
173
|
+
"""Get summary of collected metrics"""
|
174
|
+
return {
|
175
|
+
metric_type: len(data_list)
|
176
|
+
for metric_type, data_list in self.metrics.items()
|
177
|
+
}
|
178
|
+
|
179
|
+
# Create singleton instance
|
180
|
+
collector = Collector()
|
181
|
+
|
182
|
+
def capture(module_type="function"):
|
183
|
+
"""Decorator to capture metrics for any function"""
|
184
|
+
def decorator(func):
|
185
|
+
def wrapper(*args, **kwargs):
|
186
|
+
start_time = time.time()
|
187
|
+
success = False
|
188
|
+
error = None
|
189
|
+
|
190
|
+
try:
|
191
|
+
result = func(*args, **kwargs)
|
192
|
+
success = True
|
193
|
+
return result
|
194
|
+
except Exception as e:
|
195
|
+
error = str(e)
|
196
|
+
# Log the error to metrics before re-raising
|
197
|
+
collector.record("error", {
|
198
|
+
"function": func.__name__,
|
199
|
+
"error": error,
|
200
|
+
"args": str(args)[:100] if args else None
|
201
|
+
})
|
202
|
+
raise
|
203
|
+
finally:
|
204
|
+
execution_time = time.time() - start_time
|
205
|
+
|
206
|
+
collector.record(
|
207
|
+
module_type,
|
208
|
+
{
|
209
|
+
"function": func.__name__,
|
210
|
+
"execution_time": execution_time,
|
211
|
+
"success": success,
|
212
|
+
"error": error,
|
213
|
+
"timestamp": datetime.now().isoformat(),
|
214
|
+
"args": str(args)[:100] if args else None # Truncate for privacy
|
215
|
+
}
|
216
|
+
)
|
217
|
+
return wrapper
|
218
|
+
return decorator
|
vnai/beam/pulse.py
CHANGED
@@ -1,79 +1,108 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
self.
|
23
|
-
self.
|
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
|
-
self.
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
"
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
1
|
+
# vnai/beam/pulse.py
|
2
|
+
|
3
|
+
import threading
|
4
|
+
import time
|
5
|
+
from datetime import datetime
|
6
|
+
|
7
|
+
class Monitor:
|
8
|
+
"""Monitors system health and performance"""
|
9
|
+
|
10
|
+
_instance = None
|
11
|
+
_lock = threading.Lock()
|
12
|
+
|
13
|
+
def __new__(cls):
|
14
|
+
with cls._lock:
|
15
|
+
if cls._instance is None:
|
16
|
+
cls._instance = super(Monitor, cls).__new__(cls)
|
17
|
+
cls._instance._initialize()
|
18
|
+
return cls._instance
|
19
|
+
|
20
|
+
def _initialize(self):
|
21
|
+
"""Initialize monitor"""
|
22
|
+
self.health_status = "healthy"
|
23
|
+
self.last_check = time.time()
|
24
|
+
self.check_interval = 300 # seconds
|
25
|
+
self.error_count = 0
|
26
|
+
self.warning_count = 0
|
27
|
+
self.status_history = []
|
28
|
+
|
29
|
+
# Start background health check thread
|
30
|
+
self._start_background_check()
|
31
|
+
|
32
|
+
def _start_background_check(self):
|
33
|
+
"""Start background health check thread"""
|
34
|
+
def check_health():
|
35
|
+
while True:
|
36
|
+
try:
|
37
|
+
self.check_health()
|
38
|
+
except:
|
39
|
+
pass # Don't let errors stop the monitor
|
40
|
+
time.sleep(self.check_interval)
|
41
|
+
|
42
|
+
thread = threading.Thread(target=check_health, daemon=True)
|
43
|
+
thread.start()
|
44
|
+
|
45
|
+
def check_health(self):
|
46
|
+
"""Check system health status"""
|
47
|
+
from vnai.beam.metrics import collector
|
48
|
+
from vnai.beam.quota import guardian
|
49
|
+
|
50
|
+
# Record check time
|
51
|
+
self.last_check = time.time()
|
52
|
+
|
53
|
+
# Check metrics collector health
|
54
|
+
metrics_summary = collector.get_metrics_summary()
|
55
|
+
has_errors = metrics_summary.get("error", 0) > 0
|
56
|
+
|
57
|
+
# Check resource usage
|
58
|
+
resource_usage = guardian.usage()
|
59
|
+
high_usage = resource_usage > 80 # Over 80% of rate limits
|
60
|
+
|
61
|
+
# Determine health status
|
62
|
+
if has_errors and high_usage:
|
63
|
+
self.health_status = "critical"
|
64
|
+
self.error_count += 1
|
65
|
+
elif has_errors or high_usage:
|
66
|
+
self.health_status = "warning"
|
67
|
+
self.warning_count += 1
|
68
|
+
else:
|
69
|
+
self.health_status = "healthy"
|
70
|
+
|
71
|
+
# Record health status
|
72
|
+
self.status_history.append({
|
73
|
+
"timestamp": datetime.now().isoformat(),
|
74
|
+
"status": self.health_status,
|
75
|
+
"metrics": metrics_summary,
|
76
|
+
"resource_usage": resource_usage
|
77
|
+
})
|
78
|
+
|
79
|
+
# Keep history limited to last 10 entries
|
80
|
+
if len(self.status_history) > 10:
|
81
|
+
self.status_history = self.status_history[-10:]
|
82
|
+
|
83
|
+
return self.health_status
|
84
|
+
|
85
|
+
def report(self):
|
86
|
+
"""Get health report"""
|
87
|
+
# Ensure we have a fresh check if last one is old
|
88
|
+
if time.time() - self.last_check > self.check_interval:
|
89
|
+
self.check_health()
|
90
|
+
|
91
|
+
return {
|
92
|
+
"status": self.health_status,
|
93
|
+
"last_check": datetime.fromtimestamp(self.last_check).isoformat(),
|
94
|
+
"error_count": self.error_count,
|
95
|
+
"warning_count": self.warning_count,
|
96
|
+
"history": self.status_history[-3:], # Last 3 entries
|
97
|
+
}
|
98
|
+
|
99
|
+
def reset(self):
|
100
|
+
"""Reset health monitor"""
|
101
|
+
self.health_status = "healthy"
|
102
|
+
self.error_count = 0
|
103
|
+
self.warning_count = 0
|
104
|
+
self.status_history = []
|
105
|
+
self.last_check = time.time()
|
106
|
+
|
107
|
+
# Create singleton instance
|
108
|
+
monitor = Monitor()
|