local-deep-research 0.3.12__py3-none-any.whl → 0.4.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.
Files changed (92) hide show
  1. local_deep_research/__version__.py +1 -1
  2. local_deep_research/advanced_search_system/filters/base_filter.py +2 -3
  3. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +4 -5
  4. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +298 -0
  5. local_deep_research/advanced_search_system/findings/repository.py +0 -3
  6. local_deep_research/advanced_search_system/strategies/base_strategy.py +1 -2
  7. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +14 -18
  8. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +4 -8
  9. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +5 -6
  10. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +2 -2
  11. local_deep_research/advanced_search_system/strategies/standard_strategy.py +9 -7
  12. local_deep_research/api/benchmark_functions.py +288 -0
  13. local_deep_research/api/research_functions.py +8 -4
  14. local_deep_research/benchmarks/README.md +162 -0
  15. local_deep_research/benchmarks/__init__.py +51 -0
  16. local_deep_research/benchmarks/benchmark_functions.py +353 -0
  17. local_deep_research/benchmarks/cli/__init__.py +16 -0
  18. local_deep_research/benchmarks/cli/benchmark_commands.py +338 -0
  19. local_deep_research/benchmarks/cli.py +347 -0
  20. local_deep_research/benchmarks/comparison/__init__.py +12 -0
  21. local_deep_research/benchmarks/comparison/evaluator.py +768 -0
  22. local_deep_research/benchmarks/datasets/__init__.py +53 -0
  23. local_deep_research/benchmarks/datasets/base.py +295 -0
  24. local_deep_research/benchmarks/datasets/browsecomp.py +116 -0
  25. local_deep_research/benchmarks/datasets/custom_dataset_template.py +98 -0
  26. local_deep_research/benchmarks/datasets/simpleqa.py +74 -0
  27. local_deep_research/benchmarks/datasets/utils.py +116 -0
  28. local_deep_research/benchmarks/datasets.py +31 -0
  29. local_deep_research/benchmarks/efficiency/__init__.py +14 -0
  30. local_deep_research/benchmarks/efficiency/resource_monitor.py +367 -0
  31. local_deep_research/benchmarks/efficiency/speed_profiler.py +214 -0
  32. local_deep_research/benchmarks/evaluators/__init__.py +18 -0
  33. local_deep_research/benchmarks/evaluators/base.py +74 -0
  34. local_deep_research/benchmarks/evaluators/browsecomp.py +83 -0
  35. local_deep_research/benchmarks/evaluators/composite.py +121 -0
  36. local_deep_research/benchmarks/evaluators/simpleqa.py +271 -0
  37. local_deep_research/benchmarks/graders.py +410 -0
  38. local_deep_research/benchmarks/metrics/README.md +80 -0
  39. local_deep_research/benchmarks/metrics/__init__.py +24 -0
  40. local_deep_research/benchmarks/metrics/calculation.py +385 -0
  41. local_deep_research/benchmarks/metrics/reporting.py +155 -0
  42. local_deep_research/benchmarks/metrics/visualization.py +205 -0
  43. local_deep_research/benchmarks/metrics.py +11 -0
  44. local_deep_research/benchmarks/optimization/__init__.py +32 -0
  45. local_deep_research/benchmarks/optimization/api.py +274 -0
  46. local_deep_research/benchmarks/optimization/metrics.py +20 -0
  47. local_deep_research/benchmarks/optimization/optuna_optimizer.py +1163 -0
  48. local_deep_research/benchmarks/runners.py +434 -0
  49. local_deep_research/benchmarks/templates.py +65 -0
  50. local_deep_research/config/llm_config.py +26 -23
  51. local_deep_research/config/search_config.py +1 -5
  52. local_deep_research/defaults/default_settings.json +108 -7
  53. local_deep_research/search_system.py +16 -8
  54. local_deep_research/utilities/db_utils.py +3 -6
  55. local_deep_research/utilities/es_utils.py +441 -0
  56. local_deep_research/utilities/log_utils.py +36 -0
  57. local_deep_research/utilities/search_utilities.py +8 -9
  58. local_deep_research/web/app.py +7 -9
  59. local_deep_research/web/app_factory.py +9 -12
  60. local_deep_research/web/database/migrations.py +8 -5
  61. local_deep_research/web/database/models.py +20 -0
  62. local_deep_research/web/database/schema_upgrade.py +5 -8
  63. local_deep_research/web/models/database.py +15 -18
  64. local_deep_research/web/routes/benchmark_routes.py +427 -0
  65. local_deep_research/web/routes/research_routes.py +13 -17
  66. local_deep_research/web/routes/settings_routes.py +264 -67
  67. local_deep_research/web/services/research_service.py +47 -57
  68. local_deep_research/web/services/settings_manager.py +1 -4
  69. local_deep_research/web/services/settings_service.py +4 -6
  70. local_deep_research/web/static/css/styles.css +12 -0
  71. local_deep_research/web/static/js/components/logpanel.js +164 -155
  72. local_deep_research/web/static/js/components/research.js +44 -3
  73. local_deep_research/web/static/js/components/settings.js +27 -0
  74. local_deep_research/web/static/js/services/socket.js +47 -0
  75. local_deep_research/web_search_engines/default_search_engines.py +38 -0
  76. local_deep_research/web_search_engines/engines/meta_search_engine.py +100 -33
  77. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +31 -17
  78. local_deep_research/web_search_engines/engines/search_engine_brave.py +8 -3
  79. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +343 -0
  80. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +14 -6
  81. local_deep_research/web_search_engines/engines/search_engine_local.py +19 -23
  82. local_deep_research/web_search_engines/engines/search_engine_local_all.py +9 -12
  83. local_deep_research/web_search_engines/engines/search_engine_searxng.py +12 -17
  84. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +8 -4
  85. local_deep_research/web_search_engines/search_engine_base.py +22 -5
  86. local_deep_research/web_search_engines/search_engine_factory.py +32 -11
  87. local_deep_research/web_search_engines/search_engines_config.py +14 -1
  88. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/METADATA +10 -2
  89. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/RECORD +92 -49
  90. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/WHEEL +0 -0
  91. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/entry_points.txt +0 -0
  92. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,116 @@
1
+ """
2
+ Utility functions for dataset handling.
3
+
4
+ This module provides utility functions for common dataset operations like
5
+ decryption, encoding detection, etc.
6
+ """
7
+
8
+ import base64
9
+ import hashlib
10
+ import logging
11
+ from typing import Dict, Any
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def derive_key(password: str, length: int) -> bytes:
17
+ """Derive a fixed-length key from the password using SHA256."""
18
+ hasher = hashlib.sha256()
19
+ hasher.update(password.encode())
20
+ key = hasher.digest()
21
+ return key * (length // len(key)) + key[: length % len(key)]
22
+
23
+
24
+ def decrypt(ciphertext_b64: str, password: str) -> str:
25
+ """
26
+ Decrypt base64-encoded ciphertext with XOR.
27
+ Uses multiple approaches to handle different encoding formats.
28
+ """
29
+ # Skip decryption for non-encoded strings
30
+ if not isinstance(ciphertext_b64, str) or len(ciphertext_b64) < 8:
31
+ return ciphertext_b64
32
+
33
+ # Skip if the string doesn't look like base64
34
+ if not all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' for c in ciphertext_b64):
35
+ return ciphertext_b64
36
+
37
+ # Attempt standard decryption
38
+ try:
39
+ encrypted = base64.b64decode(ciphertext_b64)
40
+ key = derive_key(password, len(encrypted))
41
+ decrypted = bytes(a ^ b for a, b in zip(encrypted, key))
42
+
43
+ # Check if the result looks like valid text
44
+ result = decrypted.decode('utf-8', errors='replace')
45
+
46
+ # Heuristic check - if the decrypted text is mostly ASCII and contains spaces
47
+ if all(32 <= ord(c) < 127 for c in result[:50]) and ' ' in result[:50]:
48
+ logger.debug(f"Successfully decrypted with standard method: {result[:50]}...")
49
+ return result
50
+ except Exception as e:
51
+ logger.debug(f"Standard decryption failed: {str(e)}")
52
+
53
+ # Alternative method - try using just the first part of the password
54
+ try:
55
+ if len(password) > 30:
56
+ alt_password = password.split()[0] # Use first word
57
+ encrypted = base64.b64decode(ciphertext_b64)
58
+ key = derive_key(alt_password, len(encrypted))
59
+ decrypted = bytes(a ^ b for a, b in zip(encrypted, key))
60
+
61
+ result = decrypted.decode('utf-8', errors='replace')
62
+ if all(32 <= ord(c) < 127 for c in result[:50]) and ' ' in result[:50]:
63
+ logger.debug(f"Successfully decrypted with alternate method 1: {result[:50]}...")
64
+ return result
65
+ except Exception:
66
+ pass
67
+
68
+ # Alternative method 2 - try using the GUID part
69
+ try:
70
+ if "GUID" in password:
71
+ guid_part = password.split("GUID")[1].strip()
72
+ encrypted = base64.b64decode(ciphertext_b64)
73
+ key = derive_key(guid_part, len(encrypted))
74
+ decrypted = bytes(a ^ b for a, b in zip(encrypted, key))
75
+
76
+ result = decrypted.decode('utf-8', errors='replace')
77
+ if all(32 <= ord(c) < 127 for c in result[:50]) and ' ' in result[:50]:
78
+ logger.debug(f"Successfully decrypted with GUID method: {result[:50]}...")
79
+ return result
80
+ except Exception:
81
+ pass
82
+
83
+ # Alternative method 3 - hardcoded key for BrowseComp
84
+ try:
85
+ hardcoded_key = "MHGGF2022!" # Known key for BrowseComp dataset
86
+ encrypted = base64.b64decode(ciphertext_b64)
87
+ key = derive_key(hardcoded_key, len(encrypted))
88
+ decrypted = bytes(a ^ b for a, b in zip(encrypted, key))
89
+
90
+ result = decrypted.decode('utf-8', errors='replace')
91
+ if all(32 <= ord(c) < 127 for c in result[:50]) and ' ' in result[:50]:
92
+ logger.debug(f"Successfully decrypted with hardcoded key: {result[:50]}...")
93
+ return result
94
+ except Exception:
95
+ pass
96
+
97
+ # If all attempts fail, return the original
98
+ logger.debug(f"All decryption attempts failed for: {ciphertext_b64[:20]}...")
99
+ return ciphertext_b64
100
+
101
+
102
+ def get_known_answer_map() -> Dict[str, str]:
103
+ """Get a mapping of known encrypted answers to their decrypted values.
104
+
105
+ This function maintains a catalog of known encrypted strings that
106
+ couldn't be automatically decrypted, along with their verified
107
+ plaintext values.
108
+
109
+ Returns:
110
+ Dictionary mapping encrypted strings to their plaintext values.
111
+ """
112
+ return {
113
+ "dFoTn+K+bcdyWg==": "Tooth Rock",
114
+ "ERFIwA==": "1945",
115
+ # Add more mappings as they are discovered during benchmark runs
116
+ }
@@ -0,0 +1,31 @@
1
+ """
2
+ Dataset handling for benchmarks.
3
+
4
+ This is a legacy module that provides backwards compatibility with the old
5
+ dataset handling functions. New code should use the classes in the datasets
6
+ package directly.
7
+
8
+ Notes on BrowseComp dataset:
9
+ - BrowseComp data is encrypted with the canary string used as the decryption key
10
+ - The decrypt() function handles decrypting both problems and answers
11
+ - For some examples where standard decryption doesn't work, we use additional methods:
12
+ 1. Try using various parts of the canary string as the key
13
+ 2. Try using hardcoded keys that are known to work
14
+ 3. Use a manual mapping for specific encrypted strings that have been verified
15
+ """
16
+
17
+ import logging
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ from .datasets import DatasetRegistry, load_dataset
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Re-export the default dataset URLs
25
+ from .datasets import DEFAULT_DATASET_URLS
26
+
27
+ # Re-export the get_available_datasets function
28
+ from .datasets import get_available_datasets
29
+
30
+ # Re-export the load_dataset function
31
+ __all__ = ['DEFAULT_DATASET_URLS', 'get_available_datasets', 'load_dataset']
@@ -0,0 +1,14 @@
1
+ """
2
+ Efficiency submodule for speed and resource monitoring in Local Deep Research.
3
+
4
+ This module provides tools for measuring and optimizing execution speed
5
+ and resource usage of the research system.
6
+ """
7
+
8
+ from local_deep_research.benchmarks.efficiency.speed_profiler import SpeedProfiler
9
+ from local_deep_research.benchmarks.efficiency.resource_monitor import ResourceMonitor
10
+
11
+ __all__ = [
12
+ 'SpeedProfiler',
13
+ 'ResourceMonitor',
14
+ ]
@@ -0,0 +1,367 @@
1
+ """
2
+ Resource monitoring tools for Local Deep Research.
3
+
4
+ This module provides functionality for tracking CPU, memory and other
5
+ system resource usage during the research process.
6
+ """
7
+
8
+ import logging
9
+ import threading
10
+ import time
11
+ from contextlib import contextmanager
12
+ from typing import Dict, List, Optional, Any, Callable, Tuple
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Try to import psutil, but don't fail if not available
17
+ try:
18
+ import psutil
19
+ PSUTIL_AVAILABLE = True
20
+ except ImportError:
21
+ PSUTIL_AVAILABLE = False
22
+ logger.warning("psutil not available, resource monitoring will be limited")
23
+
24
+
25
+ class ResourceMonitor:
26
+ """
27
+ Monitor system resource usage during research.
28
+
29
+ This class provides methods for tracking CPU, memory, and disk usage
30
+ during system execution. It can be used to identify resource bottlenecks
31
+ and optimize configurations for different hardware environments.
32
+ """
33
+
34
+ def __init__(self,
35
+ sampling_interval: float = 1.0,
36
+ track_process: bool = True,
37
+ track_system: bool = True):
38
+ """
39
+ Initialize the resource monitor.
40
+
41
+ Args:
42
+ sampling_interval: Seconds between resource usage measurements
43
+ track_process: Whether to track this process's resource usage
44
+ track_system: Whether to track overall system resource usage
45
+ """
46
+ self.sampling_interval = sampling_interval
47
+ self.track_process = track_process
48
+ self.track_system = track_system
49
+
50
+ self.monitoring = False
51
+ self.monitor_thread = None
52
+
53
+ # Resource usage data
54
+ self.process_data = []
55
+ self.system_data = []
56
+ self.start_time = None
57
+ self.end_time = None
58
+
59
+ # Check if we can monitor resources
60
+ self.can_monitor = PSUTIL_AVAILABLE
61
+ if not self.can_monitor:
62
+ logger.warning("Resource monitoring requires psutil. Install with: pip install psutil")
63
+
64
+ def start(self):
65
+ """Start monitoring resource usage."""
66
+ if not self.can_monitor:
67
+ logger.warning("Resource monitoring not available (psutil not installed)")
68
+ return
69
+
70
+ if self.monitoring:
71
+ logger.warning("Resource monitoring already started")
72
+ return
73
+
74
+ self.process_data = []
75
+ self.system_data = []
76
+ self.start_time = time.time()
77
+ self.monitoring = True
78
+
79
+ # Start monitoring in a background thread
80
+ self.monitor_thread = threading.Thread(
81
+ target=self._monitor_resources,
82
+ daemon=True
83
+ )
84
+ self.monitor_thread.start()
85
+
86
+ logger.info(f"Resource monitoring started with {self.sampling_interval}s interval")
87
+
88
+ def stop(self):
89
+ """Stop monitoring resource usage."""
90
+ if not self.monitoring:
91
+ return
92
+
93
+ self.monitoring = False
94
+ self.end_time = time.time()
95
+
96
+ # Wait for the monitoring thread to finish
97
+ if self.monitor_thread:
98
+ self.monitor_thread.join(timeout=2.0)
99
+ self.monitor_thread = None
100
+
101
+ logger.info("Resource monitoring stopped")
102
+
103
+ def _monitor_resources(self):
104
+ """Background thread that collects resource usage data."""
105
+ if not PSUTIL_AVAILABLE:
106
+ return
107
+
108
+ # Get this process
109
+ current_process = psutil.Process()
110
+
111
+ while self.monitoring:
112
+ timestamp = time.time()
113
+
114
+ try:
115
+ # Monitor this process
116
+ if self.track_process:
117
+ process_cpu = current_process.cpu_percent(interval=None)
118
+ process_memory = current_process.memory_info()
119
+
120
+ self.process_data.append({
121
+ 'timestamp': timestamp,
122
+ 'cpu_percent': process_cpu,
123
+ 'memory_rss': process_memory.rss, # Resident Set Size in bytes
124
+ 'memory_vms': process_memory.vms, # Virtual Memory Size in bytes
125
+ 'memory_shared': getattr(process_memory, 'shared', 0),
126
+ 'num_threads': current_process.num_threads(),
127
+ 'open_files': len(current_process.open_files()),
128
+ 'status': current_process.status()
129
+ })
130
+
131
+ # Monitor overall system
132
+ if self.track_system:
133
+ system_cpu = psutil.cpu_percent(interval=None)
134
+ system_memory = psutil.virtual_memory()
135
+ system_disk = psutil.disk_usage('/')
136
+
137
+ self.system_data.append({
138
+ 'timestamp': timestamp,
139
+ 'cpu_percent': system_cpu,
140
+ 'memory_total': system_memory.total,
141
+ 'memory_available': system_memory.available,
142
+ 'memory_used': system_memory.used,
143
+ 'memory_percent': system_memory.percent,
144
+ 'disk_total': system_disk.total,
145
+ 'disk_used': system_disk.used,
146
+ 'disk_percent': system_disk.percent
147
+ })
148
+
149
+ except Exception as e:
150
+ logger.error(f"Error monitoring resources: {str(e)}")
151
+
152
+ # Sleep until next sampling interval
153
+ time.sleep(self.sampling_interval)
154
+
155
+ @contextmanager
156
+ def monitor(self):
157
+ """
158
+ Context manager for monitoring resources during a block of code.
159
+
160
+ Example:
161
+ with resource_monitor.monitor():
162
+ # Code to monitor
163
+ do_something_resource_intensive()
164
+ """
165
+ self.start()
166
+ try:
167
+ yield
168
+ finally:
169
+ self.stop()
170
+
171
+ def get_process_stats(self) -> Dict[str, Any]:
172
+ """
173
+ Get statistics about this process's resource usage.
174
+
175
+ Returns:
176
+ Dictionary with process resource usage statistics
177
+ """
178
+ if not self.process_data:
179
+ return {}
180
+
181
+ # Extract data series
182
+ timestamps = [d['timestamp'] for d in self.process_data]
183
+ cpu_values = [d['cpu_percent'] for d in self.process_data]
184
+ memory_values = [d['memory_rss'] / (1024 * 1024) for d in self.process_data] # Convert to MB
185
+
186
+ # Calculate statistics
187
+ stats = {
188
+ 'start_time': self.start_time,
189
+ 'end_time': self.end_time,
190
+ 'duration': self.end_time - self.start_time if self.end_time else None,
191
+ 'sample_count': len(self.process_data),
192
+ 'cpu_min': min(cpu_values) if cpu_values else None,
193
+ 'cpu_max': max(cpu_values) if cpu_values else None,
194
+ 'cpu_avg': sum(cpu_values) / len(cpu_values) if cpu_values else None,
195
+ 'memory_min_mb': min(memory_values) if memory_values else None,
196
+ 'memory_max_mb': max(memory_values) if memory_values else None,
197
+ 'memory_avg_mb': sum(memory_values) / len(memory_values) if memory_values else None,
198
+ 'thread_max': max(d['num_threads'] for d in self.process_data) if self.process_data else None,
199
+ }
200
+
201
+ return stats
202
+
203
+ def get_system_stats(self) -> Dict[str, Any]:
204
+ """
205
+ Get statistics about overall system resource usage.
206
+
207
+ Returns:
208
+ Dictionary with system resource usage statistics
209
+ """
210
+ if not self.system_data:
211
+ return {}
212
+
213
+ # Extract data series
214
+ timestamps = [d['timestamp'] for d in self.system_data]
215
+ cpu_values = [d['cpu_percent'] for d in self.system_data]
216
+ memory_values = [d['memory_percent'] for d in self.system_data]
217
+ disk_values = [d['disk_percent'] for d in self.system_data]
218
+
219
+ # Calculate statistics
220
+ stats = {
221
+ 'start_time': self.start_time,
222
+ 'end_time': self.end_time,
223
+ 'duration': self.end_time - self.start_time if self.end_time else None,
224
+ 'sample_count': len(self.system_data),
225
+ 'cpu_min': min(cpu_values) if cpu_values else None,
226
+ 'cpu_max': max(cpu_values) if cpu_values else None,
227
+ 'cpu_avg': sum(cpu_values) / len(cpu_values) if cpu_values else None,
228
+ 'memory_min_percent': min(memory_values) if memory_values else None,
229
+ 'memory_max_percent': max(memory_values) if memory_values else None,
230
+ 'memory_avg_percent': sum(memory_values) / len(memory_values) if memory_values else None,
231
+ 'disk_min_percent': min(disk_values) if disk_values else None,
232
+ 'disk_max_percent': max(disk_values) if disk_values else None,
233
+ 'disk_avg_percent': sum(disk_values) / len(disk_values) if disk_values else None,
234
+ 'memory_total_gb': self.system_data[0]['memory_total'] / (1024**3) if self.system_data else None,
235
+ 'disk_total_gb': self.system_data[0]['disk_total'] / (1024**3) if self.system_data else None,
236
+ }
237
+
238
+ return stats
239
+
240
+ def get_combined_stats(self) -> Dict[str, Any]:
241
+ """
242
+ Get combined resource usage statistics.
243
+
244
+ Returns:
245
+ Dictionary with both process and system statistics
246
+ """
247
+ process_stats = self.get_process_stats()
248
+ system_stats = self.get_system_stats()
249
+
250
+ # Combine stats
251
+ stats = {
252
+ 'start_time': self.start_time,
253
+ 'end_time': self.end_time,
254
+ 'duration': self.end_time - self.start_time if self.end_time else None,
255
+ }
256
+
257
+ # Add process stats with 'process_' prefix
258
+ for key, value in process_stats.items():
259
+ if key not in ['start_time', 'end_time', 'duration']:
260
+ stats[f'process_{key}'] = value
261
+
262
+ # Add system stats with 'system_' prefix
263
+ for key, value in system_stats.items():
264
+ if key not in ['start_time', 'end_time', 'duration']:
265
+ stats[f'system_{key}'] = value
266
+
267
+ # Calculate derived metrics
268
+ if (process_stats.get('memory_max_mb') is not None and
269
+ system_stats.get('memory_total_gb') is not None):
270
+
271
+ # Process memory as percentage of total system memory
272
+ system_memory_mb = system_stats['memory_total_gb'] * 1024
273
+ stats['process_memory_percent'] = (
274
+ (process_stats['memory_max_mb'] / system_memory_mb) * 100
275
+ if system_memory_mb > 0 else 0
276
+ )
277
+
278
+ return stats
279
+
280
+ def print_summary(self):
281
+ """Print a formatted summary of resource usage."""
282
+ process_stats = self.get_process_stats()
283
+ system_stats = self.get_system_stats()
284
+
285
+ print("\n===== RESOURCE USAGE SUMMARY =====")
286
+
287
+ if process_stats:
288
+ print("\n--- Process Resources ---")
289
+ print(f"CPU usage: {process_stats.get('cpu_avg', 0):.1f}% avg, "
290
+ f"{process_stats.get('cpu_max', 0):.1f}% peak")
291
+ print(f"Memory usage: {process_stats.get('memory_avg_mb', 0):.1f} MB avg, "
292
+ f"{process_stats.get('memory_max_mb', 0):.1f} MB peak")
293
+ print(f"Threads: {process_stats.get('thread_max', 0)} max")
294
+
295
+ if system_stats:
296
+ print("\n--- System Resources ---")
297
+ print(f"CPU usage: {system_stats.get('cpu_avg', 0):.1f}% avg, "
298
+ f"{system_stats.get('cpu_max', 0):.1f}% peak")
299
+ print(f"Memory usage: {system_stats.get('memory_avg_percent', 0):.1f}% avg, "
300
+ f"{system_stats.get('memory_max_percent', 0):.1f}% peak "
301
+ f"(Total: {system_stats.get('memory_total_gb', 0):.1f} GB)")
302
+ print(f"Disk usage: {system_stats.get('disk_avg_percent', 0):.1f}% avg "
303
+ f"(Total: {system_stats.get('disk_total_gb', 0):.1f} GB)")
304
+
305
+ print("\n===================================")
306
+
307
+ def export_data(self) -> Dict[str, Any]:
308
+ """
309
+ Export all collected data.
310
+
311
+ Returns:
312
+ Dictionary with all collected resource usage data
313
+ """
314
+ return {
315
+ 'start_time': self.start_time,
316
+ 'end_time': self.end_time,
317
+ 'sampling_interval': self.sampling_interval,
318
+ 'process_data': self.process_data,
319
+ 'system_data': self.system_data
320
+ }
321
+
322
+
323
+ def check_system_resources() -> Dict[str, Any]:
324
+ """
325
+ Check current system resources.
326
+
327
+ Returns:
328
+ Dictionary with current resource usage information
329
+ """
330
+ if not PSUTIL_AVAILABLE:
331
+ return {
332
+ 'error': 'psutil not available',
333
+ 'available': False
334
+ }
335
+
336
+ try:
337
+ # Get basic system information
338
+ cpu_count = psutil.cpu_count(logical=True)
339
+ cpu_physical = psutil.cpu_count(logical=False)
340
+ cpu_percent = psutil.cpu_percent(interval=0.1)
341
+
342
+ memory = psutil.virtual_memory()
343
+ disk = psutil.disk_usage('/')
344
+
345
+ # Format results
346
+ result = {
347
+ 'available': True,
348
+ 'cpu_count': cpu_count,
349
+ 'cpu_physical': cpu_physical,
350
+ 'cpu_percent': cpu_percent,
351
+ 'memory_total_gb': memory.total / (1024**3),
352
+ 'memory_available_gb': memory.available / (1024**3),
353
+ 'memory_used_gb': memory.used / (1024**3),
354
+ 'memory_percent': memory.percent,
355
+ 'disk_total_gb': disk.total / (1024**3),
356
+ 'disk_free_gb': disk.free / (1024**3),
357
+ 'disk_percent': disk.percent
358
+ }
359
+
360
+ return result
361
+
362
+ except Exception as e:
363
+ logger.error(f"Error checking system resources: {str(e)}")
364
+ return {
365
+ 'error': str(e),
366
+ 'available': False
367
+ }