codeshield-ai 0.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.
- codeshield/__init__.py +62 -0
- codeshield/api_server.py +438 -0
- codeshield/cli.py +48 -0
- codeshield/contextvault/__init__.py +1 -0
- codeshield/contextvault/capture.py +174 -0
- codeshield/contextvault/restore.py +115 -0
- codeshield/mcp/__init__.py +1 -0
- codeshield/mcp/hooks.py +65 -0
- codeshield/mcp/server.py +319 -0
- codeshield/styleforge/__init__.py +1 -0
- codeshield/styleforge/corrector.py +298 -0
- codeshield/trustgate/__init__.py +1 -0
- codeshield/trustgate/checker.py +384 -0
- codeshield/trustgate/sandbox.py +101 -0
- codeshield/utils/__init__.py +9 -0
- codeshield/utils/daytona.py +233 -0
- codeshield/utils/leanmcp.py +258 -0
- codeshield/utils/llm.py +423 -0
- codeshield/utils/metrics.py +543 -0
- codeshield/utils/token_optimizer.py +605 -0
- codeshield_ai-0.1.0.dist-info/METADATA +565 -0
- codeshield_ai-0.1.0.dist-info/RECORD +24 -0
- codeshield_ai-0.1.0.dist-info/WHEEL +4 -0
- codeshield_ai-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CodeShield Metrics - Transparent Statistics Tracking
|
|
3
|
+
|
|
4
|
+
Provides honest, verifiable metrics for all features:
|
|
5
|
+
- TrustGate: Detection rates, fix accuracy, execution stats
|
|
6
|
+
- StyleForge: Convention detection accuracy, correction counts
|
|
7
|
+
- ContextVault: Storage stats, restore success rates
|
|
8
|
+
- LLM: Token usage, cost efficiency, provider performance
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
import json
|
|
13
|
+
import sqlite3
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from datetime import datetime, timedelta
|
|
16
|
+
from dataclasses import dataclass, field, asdict
|
|
17
|
+
from typing import Optional, Dict, List, Any
|
|
18
|
+
from collections import defaultdict
|
|
19
|
+
from threading import Lock
|
|
20
|
+
from contextlib import contextmanager
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DB_PATH = Path.home() / ".codeshield" / "metrics.sqlite"
|
|
24
|
+
|
|
25
|
+
# Thread-safe lock for metrics updates
|
|
26
|
+
_metrics_lock = Lock()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class TrustGateMetrics:
|
|
31
|
+
"""TrustGate verification metrics"""
|
|
32
|
+
total_verifications: int = 0
|
|
33
|
+
syntax_errors_detected: int = 0
|
|
34
|
+
missing_imports_detected: int = 0
|
|
35
|
+
undefined_names_detected: int = 0
|
|
36
|
+
auto_fixes_applied: int = 0
|
|
37
|
+
sandbox_executions: int = 0
|
|
38
|
+
sandbox_successes: int = 0
|
|
39
|
+
sandbox_failures: int = 0
|
|
40
|
+
total_processing_time_ms: int = 0
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def detection_rate(self) -> float:
|
|
44
|
+
"""Percentage of verifications that found issues"""
|
|
45
|
+
if self.total_verifications == 0:
|
|
46
|
+
return 0.0
|
|
47
|
+
issues = self.syntax_errors_detected + self.missing_imports_detected + self.undefined_names_detected
|
|
48
|
+
return min(100.0, (issues / max(1, self.total_verifications)) * 100)
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def fix_success_rate(self) -> float:
|
|
52
|
+
"""Percentage of issues that were auto-fixed"""
|
|
53
|
+
issues = self.syntax_errors_detected + self.missing_imports_detected + self.undefined_names_detected
|
|
54
|
+
if issues == 0:
|
|
55
|
+
return 100.0 # No issues = nothing to fix
|
|
56
|
+
return (self.auto_fixes_applied / issues) * 100
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def sandbox_success_rate(self) -> float:
|
|
60
|
+
"""Sandbox execution success rate"""
|
|
61
|
+
if self.sandbox_executions == 0:
|
|
62
|
+
return 0.0
|
|
63
|
+
return (self.sandbox_successes / self.sandbox_executions) * 100
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def avg_processing_time_ms(self) -> float:
|
|
67
|
+
"""Average processing time per verification"""
|
|
68
|
+
if self.total_verifications == 0:
|
|
69
|
+
return 0.0
|
|
70
|
+
return self.total_processing_time_ms / self.total_verifications
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> dict:
|
|
73
|
+
return {
|
|
74
|
+
**asdict(self),
|
|
75
|
+
"detection_rate": round(self.detection_rate, 2),
|
|
76
|
+
"fix_success_rate": round(self.fix_success_rate, 2),
|
|
77
|
+
"sandbox_success_rate": round(self.sandbox_success_rate, 2),
|
|
78
|
+
"avg_processing_time_ms": round(self.avg_processing_time_ms, 2),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class StyleForgeMetrics:
|
|
84
|
+
"""StyleForge convention metrics"""
|
|
85
|
+
total_checks: int = 0
|
|
86
|
+
conventions_detected: int = 0
|
|
87
|
+
naming_issues_found: int = 0
|
|
88
|
+
corrections_suggested: int = 0
|
|
89
|
+
corrections_applied: int = 0
|
|
90
|
+
codebases_analyzed: int = 0
|
|
91
|
+
total_processing_time_ms: int = 0
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def detection_accuracy(self) -> float:
|
|
95
|
+
"""Ratio of issues found to checks performed"""
|
|
96
|
+
if self.total_checks == 0:
|
|
97
|
+
return 0.0
|
|
98
|
+
return (self.naming_issues_found / self.total_checks) * 100
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def correction_rate(self) -> float:
|
|
102
|
+
"""Percentage of suggestions that were applied"""
|
|
103
|
+
if self.corrections_suggested == 0:
|
|
104
|
+
return 0.0
|
|
105
|
+
return (self.corrections_applied / self.corrections_suggested) * 100
|
|
106
|
+
|
|
107
|
+
def to_dict(self) -> dict:
|
|
108
|
+
return {
|
|
109
|
+
**asdict(self),
|
|
110
|
+
"detection_accuracy": round(self.detection_accuracy, 2),
|
|
111
|
+
"correction_rate": round(self.correction_rate, 2),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class ContextVaultMetrics:
|
|
117
|
+
"""ContextVault storage metrics"""
|
|
118
|
+
total_contexts_saved: int = 0
|
|
119
|
+
total_contexts_restored: int = 0
|
|
120
|
+
restore_successes: int = 0
|
|
121
|
+
restore_failures: int = 0
|
|
122
|
+
contexts_deleted: int = 0
|
|
123
|
+
total_files_tracked: int = 0
|
|
124
|
+
total_storage_bytes: int = 0
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def restore_success_rate(self) -> float:
|
|
128
|
+
"""Context restore success rate"""
|
|
129
|
+
total_restores = self.restore_successes + self.restore_failures
|
|
130
|
+
if total_restores == 0:
|
|
131
|
+
return 0.0
|
|
132
|
+
return (self.restore_successes / total_restores) * 100
|
|
133
|
+
|
|
134
|
+
def to_dict(self) -> dict:
|
|
135
|
+
return {
|
|
136
|
+
**asdict(self),
|
|
137
|
+
"restore_success_rate": round(self.restore_success_rate, 2),
|
|
138
|
+
"storage_mb": round(self.total_storage_bytes / (1024 * 1024), 3),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class TokenMetrics:
|
|
144
|
+
"""LLM Token Usage Metrics - For Cost Efficiency Tracking"""
|
|
145
|
+
total_input_tokens: int = 0
|
|
146
|
+
total_output_tokens: int = 0
|
|
147
|
+
total_tokens: int = 0
|
|
148
|
+
total_requests: int = 0
|
|
149
|
+
successful_requests: int = 0
|
|
150
|
+
failed_requests: int = 0
|
|
151
|
+
|
|
152
|
+
# Provider-specific tracking
|
|
153
|
+
provider_tokens: Dict[str, dict] = field(default_factory=lambda: defaultdict(lambda: {
|
|
154
|
+
"input": 0, "output": 0, "total": 0, "requests": 0, "cost_usd": 0.0
|
|
155
|
+
}))
|
|
156
|
+
|
|
157
|
+
# Cost per 1K tokens (estimates)
|
|
158
|
+
COST_PER_1K = {
|
|
159
|
+
"cometapi": {"input": 0.0001, "output": 0.0002}, # Free tier mostly
|
|
160
|
+
"novita": {"input": 0.0005, "output": 0.001},
|
|
161
|
+
"aiml": {"input": 0.001, "output": 0.002},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def token_efficiency(self) -> float:
|
|
166
|
+
"""Output tokens per input token (higher = more efficient responses)"""
|
|
167
|
+
if self.total_input_tokens == 0:
|
|
168
|
+
return 0.0
|
|
169
|
+
return self.total_output_tokens / self.total_input_tokens
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def avg_tokens_per_request(self) -> float:
|
|
173
|
+
"""Average tokens used per request"""
|
|
174
|
+
if self.total_requests == 0:
|
|
175
|
+
return 0.0
|
|
176
|
+
return self.total_tokens / self.total_requests
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def estimated_cost_usd(self) -> float:
|
|
180
|
+
"""Estimated total cost across all providers"""
|
|
181
|
+
total_cost = 0.0
|
|
182
|
+
for provider, stats in self.provider_tokens.items():
|
|
183
|
+
if isinstance(stats, dict):
|
|
184
|
+
total_cost += stats.get("cost_usd", 0.0)
|
|
185
|
+
return total_cost
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def success_rate(self) -> float:
|
|
189
|
+
"""LLM request success rate"""
|
|
190
|
+
if self.total_requests == 0:
|
|
191
|
+
return 0.0
|
|
192
|
+
return (self.successful_requests / self.total_requests) * 100
|
|
193
|
+
|
|
194
|
+
def to_dict(self) -> dict:
|
|
195
|
+
return {
|
|
196
|
+
"total_input_tokens": self.total_input_tokens,
|
|
197
|
+
"total_output_tokens": self.total_output_tokens,
|
|
198
|
+
"total_tokens": self.total_tokens,
|
|
199
|
+
"total_requests": self.total_requests,
|
|
200
|
+
"successful_requests": self.successful_requests,
|
|
201
|
+
"failed_requests": self.failed_requests,
|
|
202
|
+
"token_efficiency": round(self.token_efficiency, 3),
|
|
203
|
+
"avg_tokens_per_request": round(self.avg_tokens_per_request, 1),
|
|
204
|
+
"estimated_cost_usd": round(self.estimated_cost_usd, 4),
|
|
205
|
+
"success_rate": round(self.success_rate, 2),
|
|
206
|
+
"by_provider": dict(self.provider_tokens),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class MetricsCollector:
|
|
211
|
+
"""
|
|
212
|
+
Centralized metrics collection for all CodeShield features.
|
|
213
|
+
|
|
214
|
+
Provides:
|
|
215
|
+
- Real-time statistics
|
|
216
|
+
- Persistent storage for historical data
|
|
217
|
+
- Transparent, verifiable metrics
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
_instance = None
|
|
221
|
+
_initialized = False
|
|
222
|
+
|
|
223
|
+
def __new__(cls):
|
|
224
|
+
if cls._instance is None:
|
|
225
|
+
cls._instance = super().__new__(cls)
|
|
226
|
+
return cls._instance
|
|
227
|
+
|
|
228
|
+
def __init__(self):
|
|
229
|
+
if MetricsCollector._initialized:
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
self.trustgate = TrustGateMetrics()
|
|
233
|
+
self.styleforge = StyleForgeMetrics()
|
|
234
|
+
self.contextvault = ContextVaultMetrics()
|
|
235
|
+
self.tokens = TokenMetrics()
|
|
236
|
+
|
|
237
|
+
self._session_start = datetime.now()
|
|
238
|
+
self._ensure_db()
|
|
239
|
+
self._load_from_db()
|
|
240
|
+
|
|
241
|
+
MetricsCollector._initialized = True
|
|
242
|
+
|
|
243
|
+
def _ensure_db(self):
|
|
244
|
+
"""Ensure database exists with schema"""
|
|
245
|
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
246
|
+
|
|
247
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
248
|
+
cursor = conn.cursor()
|
|
249
|
+
|
|
250
|
+
cursor.execute("""
|
|
251
|
+
CREATE TABLE IF NOT EXISTS metrics_history (
|
|
252
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
253
|
+
timestamp TEXT NOT NULL,
|
|
254
|
+
category TEXT NOT NULL,
|
|
255
|
+
metric_name TEXT NOT NULL,
|
|
256
|
+
metric_value REAL NOT NULL,
|
|
257
|
+
metadata TEXT
|
|
258
|
+
)
|
|
259
|
+
""")
|
|
260
|
+
|
|
261
|
+
cursor.execute("""
|
|
262
|
+
CREATE TABLE IF NOT EXISTS metrics_snapshot (
|
|
263
|
+
category TEXT PRIMARY KEY,
|
|
264
|
+
data TEXT NOT NULL,
|
|
265
|
+
updated_at TEXT NOT NULL
|
|
266
|
+
)
|
|
267
|
+
""")
|
|
268
|
+
|
|
269
|
+
conn.commit()
|
|
270
|
+
conn.close()
|
|
271
|
+
|
|
272
|
+
def _load_from_db(self):
|
|
273
|
+
"""Load persisted metrics"""
|
|
274
|
+
try:
|
|
275
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
276
|
+
cursor = conn.cursor()
|
|
277
|
+
|
|
278
|
+
cursor.execute("SELECT category, data FROM metrics_snapshot")
|
|
279
|
+
rows = cursor.fetchall()
|
|
280
|
+
|
|
281
|
+
for category, data_json in rows:
|
|
282
|
+
data = json.loads(data_json)
|
|
283
|
+
if category == "trustgate":
|
|
284
|
+
for k, v in data.items():
|
|
285
|
+
if hasattr(self.trustgate, k) and not k.startswith("_"):
|
|
286
|
+
setattr(self.trustgate, k, v)
|
|
287
|
+
elif category == "styleforge":
|
|
288
|
+
for k, v in data.items():
|
|
289
|
+
if hasattr(self.styleforge, k) and not k.startswith("_"):
|
|
290
|
+
setattr(self.styleforge, k, v)
|
|
291
|
+
elif category == "contextvault":
|
|
292
|
+
for k, v in data.items():
|
|
293
|
+
if hasattr(self.contextvault, k) and not k.startswith("_"):
|
|
294
|
+
setattr(self.contextvault, k, v)
|
|
295
|
+
elif category == "tokens":
|
|
296
|
+
for k, v in data.items():
|
|
297
|
+
if hasattr(self.tokens, k) and not k.startswith("_") and k != "provider_tokens":
|
|
298
|
+
setattr(self.tokens, k, v)
|
|
299
|
+
|
|
300
|
+
conn.close()
|
|
301
|
+
except Exception as e:
|
|
302
|
+
print(f"Warning: Could not load metrics: {e}")
|
|
303
|
+
|
|
304
|
+
def _save_to_db(self):
|
|
305
|
+
"""Persist current metrics"""
|
|
306
|
+
try:
|
|
307
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
308
|
+
cursor = conn.cursor()
|
|
309
|
+
|
|
310
|
+
now = datetime.now().isoformat()
|
|
311
|
+
|
|
312
|
+
snapshots = [
|
|
313
|
+
("trustgate", json.dumps(asdict(self.trustgate))),
|
|
314
|
+
("styleforge", json.dumps(asdict(self.styleforge))),
|
|
315
|
+
("contextvault", json.dumps(asdict(self.contextvault))),
|
|
316
|
+
("tokens", json.dumps({
|
|
317
|
+
"total_input_tokens": self.tokens.total_input_tokens,
|
|
318
|
+
"total_output_tokens": self.tokens.total_output_tokens,
|
|
319
|
+
"total_tokens": self.tokens.total_tokens,
|
|
320
|
+
"total_requests": self.tokens.total_requests,
|
|
321
|
+
"successful_requests": self.tokens.successful_requests,
|
|
322
|
+
"failed_requests": self.tokens.failed_requests,
|
|
323
|
+
})),
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
for category, data in snapshots:
|
|
327
|
+
cursor.execute("""
|
|
328
|
+
INSERT OR REPLACE INTO metrics_snapshot (category, data, updated_at)
|
|
329
|
+
VALUES (?, ?, ?)
|
|
330
|
+
""", (category, data, now))
|
|
331
|
+
|
|
332
|
+
conn.commit()
|
|
333
|
+
conn.close()
|
|
334
|
+
except Exception as e:
|
|
335
|
+
print(f"Warning: Could not save metrics: {e}")
|
|
336
|
+
|
|
337
|
+
@contextmanager
|
|
338
|
+
def track_time(self, category: str):
|
|
339
|
+
"""Context manager to track processing time"""
|
|
340
|
+
start = time.time()
|
|
341
|
+
yield
|
|
342
|
+
elapsed_ms = int((time.time() - start) * 1000)
|
|
343
|
+
|
|
344
|
+
with _metrics_lock:
|
|
345
|
+
if category == "trustgate":
|
|
346
|
+
self.trustgate.total_processing_time_ms += elapsed_ms
|
|
347
|
+
elif category == "styleforge":
|
|
348
|
+
self.styleforge.total_processing_time_ms += elapsed_ms
|
|
349
|
+
|
|
350
|
+
# TrustGate tracking
|
|
351
|
+
def track_verification(self, syntax_error: bool = False, missing_imports: int = 0,
|
|
352
|
+
undefined_names: int = 0, auto_fixed: bool = False):
|
|
353
|
+
"""Track a TrustGate verification"""
|
|
354
|
+
with _metrics_lock:
|
|
355
|
+
self.trustgate.total_verifications += 1
|
|
356
|
+
if syntax_error:
|
|
357
|
+
self.trustgate.syntax_errors_detected += 1
|
|
358
|
+
self.trustgate.missing_imports_detected += missing_imports
|
|
359
|
+
self.trustgate.undefined_names_detected += undefined_names
|
|
360
|
+
if auto_fixed:
|
|
361
|
+
self.trustgate.auto_fixes_applied += 1
|
|
362
|
+
self._save_to_db()
|
|
363
|
+
|
|
364
|
+
def track_sandbox(self, success: bool):
|
|
365
|
+
"""Track a sandbox execution"""
|
|
366
|
+
with _metrics_lock:
|
|
367
|
+
self.trustgate.sandbox_executions += 1
|
|
368
|
+
if success:
|
|
369
|
+
self.trustgate.sandbox_successes += 1
|
|
370
|
+
else:
|
|
371
|
+
self.trustgate.sandbox_failures += 1
|
|
372
|
+
self._save_to_db()
|
|
373
|
+
|
|
374
|
+
# StyleForge tracking
|
|
375
|
+
def track_style_check(self, conventions_found: int = 0, issues_found: int = 0,
|
|
376
|
+
corrections_suggested: int = 0, corrections_applied: int = 0):
|
|
377
|
+
"""Track a StyleForge check"""
|
|
378
|
+
with _metrics_lock:
|
|
379
|
+
self.styleforge.total_checks += 1
|
|
380
|
+
self.styleforge.conventions_detected += conventions_found
|
|
381
|
+
self.styleforge.naming_issues_found += issues_found
|
|
382
|
+
self.styleforge.corrections_suggested += corrections_suggested
|
|
383
|
+
self.styleforge.corrections_applied += corrections_applied
|
|
384
|
+
self._save_to_db()
|
|
385
|
+
|
|
386
|
+
def track_codebase_analyzed(self):
|
|
387
|
+
"""Track a codebase analysis"""
|
|
388
|
+
with _metrics_lock:
|
|
389
|
+
self.styleforge.codebases_analyzed += 1
|
|
390
|
+
self._save_to_db()
|
|
391
|
+
|
|
392
|
+
# ContextVault tracking
|
|
393
|
+
def track_context_save(self, files_count: int = 0):
|
|
394
|
+
"""Track a context save"""
|
|
395
|
+
with _metrics_lock:
|
|
396
|
+
self.contextvault.total_contexts_saved += 1
|
|
397
|
+
self.contextvault.total_files_tracked += files_count
|
|
398
|
+
self._save_to_db()
|
|
399
|
+
|
|
400
|
+
def track_context_restore(self, success: bool):
|
|
401
|
+
"""Track a context restore"""
|
|
402
|
+
with _metrics_lock:
|
|
403
|
+
self.contextvault.total_contexts_restored += 1
|
|
404
|
+
if success:
|
|
405
|
+
self.contextvault.restore_successes += 1
|
|
406
|
+
else:
|
|
407
|
+
self.contextvault.restore_failures += 1
|
|
408
|
+
self._save_to_db()
|
|
409
|
+
|
|
410
|
+
def track_context_delete(self):
|
|
411
|
+
"""Track a context deletion"""
|
|
412
|
+
with _metrics_lock:
|
|
413
|
+
self.contextvault.contexts_deleted += 1
|
|
414
|
+
self._save_to_db()
|
|
415
|
+
|
|
416
|
+
# Token tracking
|
|
417
|
+
def track_tokens(self, provider: str, input_tokens: int, output_tokens: int,
|
|
418
|
+
success: bool = True):
|
|
419
|
+
"""Track LLM token usage"""
|
|
420
|
+
with _metrics_lock:
|
|
421
|
+
self.tokens.total_input_tokens += input_tokens
|
|
422
|
+
self.tokens.total_output_tokens += output_tokens
|
|
423
|
+
self.tokens.total_tokens += input_tokens + output_tokens
|
|
424
|
+
self.tokens.total_requests += 1
|
|
425
|
+
|
|
426
|
+
if success:
|
|
427
|
+
self.tokens.successful_requests += 1
|
|
428
|
+
else:
|
|
429
|
+
self.tokens.failed_requests += 1
|
|
430
|
+
|
|
431
|
+
# Provider-specific tracking
|
|
432
|
+
if provider not in self.tokens.provider_tokens:
|
|
433
|
+
self.tokens.provider_tokens[provider] = {
|
|
434
|
+
"input": 0, "output": 0, "total": 0, "requests": 0, "cost_usd": 0.0
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
stats = self.tokens.provider_tokens[provider]
|
|
438
|
+
stats["input"] += input_tokens
|
|
439
|
+
stats["output"] += output_tokens
|
|
440
|
+
stats["total"] += input_tokens + output_tokens
|
|
441
|
+
stats["requests"] += 1
|
|
442
|
+
|
|
443
|
+
# Estimate cost
|
|
444
|
+
costs = self.tokens.COST_PER_1K.get(provider, {"input": 0.001, "output": 0.002})
|
|
445
|
+
stats["cost_usd"] += (input_tokens / 1000) * costs["input"]
|
|
446
|
+
stats["cost_usd"] += (output_tokens / 1000) * costs["output"]
|
|
447
|
+
|
|
448
|
+
self._save_to_db()
|
|
449
|
+
|
|
450
|
+
def get_summary(self) -> dict:
|
|
451
|
+
"""Get comprehensive metrics summary"""
|
|
452
|
+
session_duration = (datetime.now() - self._session_start).total_seconds()
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
"session": {
|
|
456
|
+
"started_at": self._session_start.isoformat(),
|
|
457
|
+
"duration_seconds": round(session_duration, 2),
|
|
458
|
+
"duration_human": str(timedelta(seconds=int(session_duration))),
|
|
459
|
+
},
|
|
460
|
+
"trustgate": self.trustgate.to_dict(),
|
|
461
|
+
"styleforge": self.styleforge.to_dict(),
|
|
462
|
+
"contextvault": self.contextvault.to_dict(),
|
|
463
|
+
"tokens": self.tokens.to_dict(),
|
|
464
|
+
"totals": {
|
|
465
|
+
"total_operations": (
|
|
466
|
+
self.trustgate.total_verifications +
|
|
467
|
+
self.styleforge.total_checks +
|
|
468
|
+
self.contextvault.total_contexts_saved +
|
|
469
|
+
self.contextvault.total_contexts_restored +
|
|
470
|
+
self.tokens.total_requests
|
|
471
|
+
),
|
|
472
|
+
"total_issues_detected": (
|
|
473
|
+
self.trustgate.syntax_errors_detected +
|
|
474
|
+
self.trustgate.missing_imports_detected +
|
|
475
|
+
self.trustgate.undefined_names_detected +
|
|
476
|
+
self.styleforge.naming_issues_found
|
|
477
|
+
),
|
|
478
|
+
"total_auto_fixes": (
|
|
479
|
+
self.trustgate.auto_fixes_applied +
|
|
480
|
+
self.styleforge.corrections_applied
|
|
481
|
+
),
|
|
482
|
+
},
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
def reset(self):
|
|
486
|
+
"""Reset all metrics (for testing)"""
|
|
487
|
+
with _metrics_lock:
|
|
488
|
+
self.trustgate = TrustGateMetrics()
|
|
489
|
+
self.styleforge = StyleForgeMetrics()
|
|
490
|
+
self.contextvault = ContextVaultMetrics()
|
|
491
|
+
self.tokens = TokenMetrics()
|
|
492
|
+
self._session_start = datetime.now()
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
# Singleton instance
|
|
496
|
+
def get_metrics() -> MetricsCollector:
|
|
497
|
+
"""Get the global metrics collector instance"""
|
|
498
|
+
return MetricsCollector()
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# Convenience decorators for automatic tracking
|
|
502
|
+
def track_trustgate_verification(func):
|
|
503
|
+
"""Decorator to track TrustGate verifications"""
|
|
504
|
+
def wrapper(*args, **kwargs):
|
|
505
|
+
metrics = get_metrics()
|
|
506
|
+
with metrics.track_time("trustgate"):
|
|
507
|
+
result = func(*args, **kwargs)
|
|
508
|
+
|
|
509
|
+
# Extract metrics from result if it has the right attributes
|
|
510
|
+
if hasattr(result, 'issues'):
|
|
511
|
+
syntax_error = any(i.severity == "error" and "Syntax" in i.message for i in result.issues)
|
|
512
|
+
missing_imports = sum(1 for i in result.issues if "Missing import" in i.message)
|
|
513
|
+
undefined = sum(1 for i in result.issues if "undefined" in i.message.lower())
|
|
514
|
+
auto_fixed = result.fixed_code is not None
|
|
515
|
+
|
|
516
|
+
metrics.track_verification(
|
|
517
|
+
syntax_error=syntax_error,
|
|
518
|
+
missing_imports=missing_imports,
|
|
519
|
+
undefined_names=undefined,
|
|
520
|
+
auto_fixed=auto_fixed
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
return result
|
|
524
|
+
return wrapper
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def track_styleforge_check(func):
|
|
528
|
+
"""Decorator to track StyleForge checks"""
|
|
529
|
+
def wrapper(*args, **kwargs):
|
|
530
|
+
metrics = get_metrics()
|
|
531
|
+
with metrics.track_time("styleforge"):
|
|
532
|
+
result = func(*args, **kwargs)
|
|
533
|
+
|
|
534
|
+
if hasattr(result, 'issues'):
|
|
535
|
+
metrics.track_style_check(
|
|
536
|
+
conventions_found=len(result.conventions_detected) if hasattr(result, 'conventions_detected') else 0,
|
|
537
|
+
issues_found=len(result.issues),
|
|
538
|
+
corrections_suggested=len(result.issues),
|
|
539
|
+
corrections_applied=1 if result.corrected_code else 0
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
return result
|
|
543
|
+
return wrapper
|