local-deep-research 0.4.4__py3-none-any.whl → 0.5.2__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 (220) hide show
  1. local_deep_research/__init__.py +7 -0
  2. local_deep_research/__version__.py +1 -1
  3. local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
  4. local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
  5. local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
  6. local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
  7. local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
  8. local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
  9. local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
  10. local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
  11. local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
  12. local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
  13. local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
  14. local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
  15. local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
  16. local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
  17. local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
  18. local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
  19. local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
  20. local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
  21. local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
  22. local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
  23. local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
  24. local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
  25. local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
  26. local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
  27. local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
  28. local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
  29. local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
  30. local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
  31. local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
  32. local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
  33. local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
  34. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
  35. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
  36. local_deep_research/advanced_search_system/findings/repository.py +54 -17
  37. local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
  38. local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
  39. local_deep_research/advanced_search_system/questions/__init__.py +16 -0
  40. local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
  41. local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
  42. local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
  43. local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
  44. local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
  45. local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
  46. local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
  47. local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
  48. local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
  49. local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
  50. local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
  51. local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
  52. local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
  53. local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
  54. local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
  55. local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
  56. local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
  57. local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
  58. local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
  59. local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
  60. local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
  61. local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
  62. local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
  63. local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
  64. local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
  65. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
  66. local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
  67. local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
  68. local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
  69. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
  70. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
  71. local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
  72. local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
  73. local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
  74. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
  75. local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
  76. local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
  77. local_deep_research/api/benchmark_functions.py +6 -2
  78. local_deep_research/api/research_functions.py +10 -4
  79. local_deep_research/benchmarks/__init__.py +9 -7
  80. local_deep_research/benchmarks/benchmark_functions.py +6 -2
  81. local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
  82. local_deep_research/benchmarks/cli.py +38 -13
  83. local_deep_research/benchmarks/comparison/__init__.py +4 -2
  84. local_deep_research/benchmarks/comparison/evaluator.py +316 -239
  85. local_deep_research/benchmarks/datasets/__init__.py +1 -1
  86. local_deep_research/benchmarks/datasets/base.py +91 -72
  87. local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
  88. local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
  89. local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
  90. local_deep_research/benchmarks/datasets/utils.py +48 -29
  91. local_deep_research/benchmarks/datasets.py +4 -11
  92. local_deep_research/benchmarks/efficiency/__init__.py +8 -4
  93. local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
  94. local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
  95. local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
  96. local_deep_research/benchmarks/evaluators/composite.py +6 -2
  97. local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
  98. local_deep_research/benchmarks/graders.py +32 -10
  99. local_deep_research/benchmarks/metrics/README.md +1 -1
  100. local_deep_research/benchmarks/metrics/calculation.py +25 -10
  101. local_deep_research/benchmarks/metrics/reporting.py +7 -3
  102. local_deep_research/benchmarks/metrics/visualization.py +42 -23
  103. local_deep_research/benchmarks/metrics.py +1 -1
  104. local_deep_research/benchmarks/optimization/__init__.py +3 -1
  105. local_deep_research/benchmarks/optimization/api.py +7 -1
  106. local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
  107. local_deep_research/benchmarks/runners.py +48 -15
  108. local_deep_research/citation_handler.py +65 -92
  109. local_deep_research/citation_handlers/__init__.py +15 -0
  110. local_deep_research/citation_handlers/base_citation_handler.py +70 -0
  111. local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
  112. local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
  113. local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
  114. local_deep_research/config/llm_config.py +271 -169
  115. local_deep_research/config/search_config.py +14 -5
  116. local_deep_research/defaults/__init__.py +0 -1
  117. local_deep_research/metrics/__init__.py +13 -0
  118. local_deep_research/metrics/database.py +58 -0
  119. local_deep_research/metrics/db_models.py +115 -0
  120. local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
  121. local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
  122. local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
  123. local_deep_research/metrics/migrate_research_ratings.py +31 -0
  124. local_deep_research/metrics/models.py +61 -0
  125. local_deep_research/metrics/pricing/__init__.py +12 -0
  126. local_deep_research/metrics/pricing/cost_calculator.py +237 -0
  127. local_deep_research/metrics/pricing/pricing_cache.py +143 -0
  128. local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
  129. local_deep_research/metrics/query_utils.py +51 -0
  130. local_deep_research/metrics/search_tracker.py +380 -0
  131. local_deep_research/metrics/token_counter.py +1078 -0
  132. local_deep_research/migrate_db.py +3 -1
  133. local_deep_research/report_generator.py +22 -8
  134. local_deep_research/search_system.py +390 -9
  135. local_deep_research/test_migration.py +15 -5
  136. local_deep_research/utilities/db_utils.py +7 -4
  137. local_deep_research/utilities/es_utils.py +115 -104
  138. local_deep_research/utilities/llm_utils.py +15 -5
  139. local_deep_research/utilities/log_utils.py +151 -0
  140. local_deep_research/utilities/search_cache.py +387 -0
  141. local_deep_research/utilities/search_utilities.py +14 -6
  142. local_deep_research/utilities/threading_utils.py +92 -0
  143. local_deep_research/utilities/url_utils.py +6 -0
  144. local_deep_research/web/api.py +347 -0
  145. local_deep_research/web/app.py +13 -17
  146. local_deep_research/web/app_factory.py +71 -66
  147. local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
  148. local_deep_research/web/database/migrations.py +20 -3
  149. local_deep_research/web/database/models.py +74 -25
  150. local_deep_research/web/database/schema_upgrade.py +49 -29
  151. local_deep_research/web/models/database.py +63 -83
  152. local_deep_research/web/routes/api_routes.py +56 -22
  153. local_deep_research/web/routes/benchmark_routes.py +4 -1
  154. local_deep_research/web/routes/globals.py +22 -0
  155. local_deep_research/web/routes/history_routes.py +71 -46
  156. local_deep_research/web/routes/metrics_routes.py +1155 -0
  157. local_deep_research/web/routes/research_routes.py +192 -54
  158. local_deep_research/web/routes/settings_routes.py +156 -55
  159. local_deep_research/web/services/research_service.py +412 -251
  160. local_deep_research/web/services/resource_service.py +36 -11
  161. local_deep_research/web/services/settings_manager.py +55 -17
  162. local_deep_research/web/services/settings_service.py +12 -4
  163. local_deep_research/web/services/socket_service.py +295 -188
  164. local_deep_research/web/static/css/custom_dropdown.css +180 -0
  165. local_deep_research/web/static/css/styles.css +39 -1
  166. local_deep_research/web/static/js/components/detail.js +633 -267
  167. local_deep_research/web/static/js/components/details.js +751 -0
  168. local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
  169. local_deep_research/web/static/js/components/fallback/ui.js +23 -23
  170. local_deep_research/web/static/js/components/history.js +76 -76
  171. local_deep_research/web/static/js/components/logpanel.js +61 -13
  172. local_deep_research/web/static/js/components/progress.js +13 -2
  173. local_deep_research/web/static/js/components/research.js +99 -12
  174. local_deep_research/web/static/js/components/results.js +239 -106
  175. local_deep_research/web/static/js/main.js +40 -40
  176. local_deep_research/web/static/js/services/audio.js +1 -1
  177. local_deep_research/web/static/js/services/formatting.js +11 -11
  178. local_deep_research/web/static/js/services/keyboard.js +157 -0
  179. local_deep_research/web/static/js/services/pdf.js +80 -80
  180. local_deep_research/web/static/sounds/README.md +1 -1
  181. local_deep_research/web/templates/base.html +1 -0
  182. local_deep_research/web/templates/components/log_panel.html +7 -1
  183. local_deep_research/web/templates/components/mobile_nav.html +1 -1
  184. local_deep_research/web/templates/components/sidebar.html +3 -0
  185. local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
  186. local_deep_research/web/templates/pages/details.html +325 -24
  187. local_deep_research/web/templates/pages/history.html +1 -1
  188. local_deep_research/web/templates/pages/metrics.html +1929 -0
  189. local_deep_research/web/templates/pages/progress.html +2 -2
  190. local_deep_research/web/templates/pages/research.html +53 -17
  191. local_deep_research/web/templates/pages/results.html +12 -1
  192. local_deep_research/web/templates/pages/star_reviews.html +803 -0
  193. local_deep_research/web/utils/formatters.py +9 -3
  194. local_deep_research/web_search_engines/default_search_engines.py +5 -3
  195. local_deep_research/web_search_engines/engines/full_search.py +8 -2
  196. local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
  197. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
  198. local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
  199. local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
  200. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
  201. local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
  202. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
  203. local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
  204. local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
  205. local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
  206. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
  207. local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
  208. local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
  209. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
  210. local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
  211. local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
  212. local_deep_research/web_search_engines/search_engine_base.py +83 -35
  213. local_deep_research/web_search_engines/search_engine_factory.py +25 -8
  214. local_deep_research/web_search_engines/search_engines_config.py +9 -3
  215. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/METADATA +7 -1
  216. local_deep_research-0.5.2.dist-info/RECORD +265 -0
  217. local_deep_research-0.4.4.dist-info/RECORD +0 -177
  218. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/WHEEL +0 -0
  219. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt +0 -0
  220. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -9,13 +9,14 @@ import logging
9
9
  import threading
10
10
  import time
11
11
  from contextlib import contextmanager
12
- from typing import Dict, List, Optional, Any, Callable, Tuple
12
+ from typing import Any, Dict
13
13
 
14
14
  logger = logging.getLogger(__name__)
15
15
 
16
16
  # Try to import psutil, but don't fail if not available
17
17
  try:
18
18
  import psutil
19
+
19
20
  PSUTIL_AVAILABLE = True
20
21
  except ImportError:
21
22
  PSUTIL_AVAILABLE = False
@@ -25,19 +26,21 @@ except ImportError:
25
26
  class ResourceMonitor:
26
27
  """
27
28
  Monitor system resource usage during research.
28
-
29
+
29
30
  This class provides methods for tracking CPU, memory, and disk usage
30
31
  during system execution. It can be used to identify resource bottlenecks
31
32
  and optimize configurations for different hardware environments.
32
33
  """
33
-
34
- def __init__(self,
35
- sampling_interval: float = 1.0,
36
- track_process: bool = True,
37
- track_system: bool = True):
34
+
35
+ def __init__(
36
+ self,
37
+ sampling_interval: float = 1.0,
38
+ track_process: bool = True,
39
+ track_system: bool = True,
40
+ ):
38
41
  """
39
42
  Initialize the resource monitor.
40
-
43
+
41
44
  Args:
42
45
  sampling_interval: Seconds between resource usage measurements
43
46
  track_process: Whether to track this process's resource usage
@@ -46,117 +49,128 @@ class ResourceMonitor:
46
49
  self.sampling_interval = sampling_interval
47
50
  self.track_process = track_process
48
51
  self.track_system = track_system
49
-
52
+
50
53
  self.monitoring = False
51
54
  self.monitor_thread = None
52
-
55
+
53
56
  # Resource usage data
54
57
  self.process_data = []
55
58
  self.system_data = []
56
59
  self.start_time = None
57
60
  self.end_time = None
58
-
61
+
59
62
  # Check if we can monitor resources
60
63
  self.can_monitor = PSUTIL_AVAILABLE
61
64
  if not self.can_monitor:
62
- logger.warning("Resource monitoring requires psutil. Install with: pip install psutil")
63
-
65
+ logger.warning(
66
+ "Resource monitoring requires psutil. Install with: pip install psutil"
67
+ )
68
+
64
69
  def start(self):
65
70
  """Start monitoring resource usage."""
66
71
  if not self.can_monitor:
67
- logger.warning("Resource monitoring not available (psutil not installed)")
72
+ logger.warning(
73
+ "Resource monitoring not available (psutil not installed)"
74
+ )
68
75
  return
69
-
76
+
70
77
  if self.monitoring:
71
78
  logger.warning("Resource monitoring already started")
72
79
  return
73
-
80
+
74
81
  self.process_data = []
75
82
  self.system_data = []
76
83
  self.start_time = time.time()
77
84
  self.monitoring = True
78
-
85
+
79
86
  # Start monitoring in a background thread
80
87
  self.monitor_thread = threading.Thread(
81
- target=self._monitor_resources,
82
- daemon=True
88
+ target=self._monitor_resources, daemon=True
83
89
  )
84
90
  self.monitor_thread.start()
85
-
86
- logger.info(f"Resource monitoring started with {self.sampling_interval}s interval")
87
-
91
+
92
+ logger.info(
93
+ f"Resource monitoring started with {self.sampling_interval}s interval"
94
+ )
95
+
88
96
  def stop(self):
89
97
  """Stop monitoring resource usage."""
90
98
  if not self.monitoring:
91
99
  return
92
-
100
+
93
101
  self.monitoring = False
94
102
  self.end_time = time.time()
95
-
103
+
96
104
  # Wait for the monitoring thread to finish
97
105
  if self.monitor_thread:
98
106
  self.monitor_thread.join(timeout=2.0)
99
107
  self.monitor_thread = None
100
-
108
+
101
109
  logger.info("Resource monitoring stopped")
102
-
110
+
103
111
  def _monitor_resources(self):
104
112
  """Background thread that collects resource usage data."""
105
113
  if not PSUTIL_AVAILABLE:
106
114
  return
107
-
115
+
108
116
  # Get this process
109
117
  current_process = psutil.Process()
110
-
118
+
111
119
  while self.monitoring:
112
120
  timestamp = time.time()
113
-
121
+
114
122
  try:
115
123
  # Monitor this process
116
124
  if self.track_process:
117
125
  process_cpu = current_process.cpu_percent(interval=None)
118
126
  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
-
127
+
128
+ self.process_data.append(
129
+ {
130
+ "timestamp": timestamp,
131
+ "cpu_percent": process_cpu,
132
+ "memory_rss": process_memory.rss, # Resident Set Size in bytes
133
+ "memory_vms": process_memory.vms, # Virtual Memory Size in bytes
134
+ "memory_shared": getattr(
135
+ process_memory, "shared", 0
136
+ ),
137
+ "num_threads": current_process.num_threads(),
138
+ "open_files": len(current_process.open_files()),
139
+ "status": current_process.status(),
140
+ }
141
+ )
142
+
131
143
  # Monitor overall system
132
144
  if self.track_system:
133
145
  system_cpu = psutil.cpu_percent(interval=None)
134
146
  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
-
147
+ system_disk = psutil.disk_usage("/")
148
+
149
+ self.system_data.append(
150
+ {
151
+ "timestamp": timestamp,
152
+ "cpu_percent": system_cpu,
153
+ "memory_total": system_memory.total,
154
+ "memory_available": system_memory.available,
155
+ "memory_used": system_memory.used,
156
+ "memory_percent": system_memory.percent,
157
+ "disk_total": system_disk.total,
158
+ "disk_used": system_disk.used,
159
+ "disk_percent": system_disk.percent,
160
+ }
161
+ )
162
+
149
163
  except Exception as e:
150
164
  logger.error(f"Error monitoring resources: {str(e)}")
151
-
165
+
152
166
  # Sleep until next sampling interval
153
167
  time.sleep(self.sampling_interval)
154
-
168
+
155
169
  @contextmanager
156
170
  def monitor(self):
157
171
  """
158
172
  Context manager for monitoring resources during a block of code.
159
-
173
+
160
174
  Example:
161
175
  with resource_monitor.monitor():
162
176
  # Code to monitor
@@ -167,201 +181,239 @@ class ResourceMonitor:
167
181
  yield
168
182
  finally:
169
183
  self.stop()
170
-
184
+
171
185
  def get_process_stats(self) -> Dict[str, Any]:
172
186
  """
173
187
  Get statistics about this process's resource usage.
174
-
188
+
175
189
  Returns:
176
190
  Dictionary with process resource usage statistics
177
191
  """
178
192
  if not self.process_data:
179
193
  return {}
180
-
194
+
181
195
  # 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
-
196
+ cpu_values = [d["cpu_percent"] for d in self.process_data]
197
+ memory_values = [
198
+ d["memory_rss"] / (1024 * 1024) for d in self.process_data
199
+ ] # Convert to MB
200
+
186
201
  # Calculate statistics
187
202
  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,
203
+ "start_time": self.start_time,
204
+ "end_time": self.end_time,
205
+ "duration": self.end_time - self.start_time
206
+ if self.end_time
207
+ else None,
208
+ "sample_count": len(self.process_data),
209
+ "cpu_min": min(cpu_values) if cpu_values else None,
210
+ "cpu_max": max(cpu_values) if cpu_values else None,
211
+ "cpu_avg": sum(cpu_values) / len(cpu_values)
212
+ if cpu_values
213
+ else None,
214
+ "memory_min_mb": min(memory_values) if memory_values else None,
215
+ "memory_max_mb": max(memory_values) if memory_values else None,
216
+ "memory_avg_mb": (
217
+ sum(memory_values) / len(memory_values)
218
+ if memory_values
219
+ else None
220
+ ),
221
+ "thread_max": (
222
+ max(d["num_threads"] for d in self.process_data)
223
+ if self.process_data
224
+ else None
225
+ ),
199
226
  }
200
-
227
+
201
228
  return stats
202
-
229
+
203
230
  def get_system_stats(self) -> Dict[str, Any]:
204
231
  """
205
232
  Get statistics about overall system resource usage.
206
-
233
+
207
234
  Returns:
208
235
  Dictionary with system resource usage statistics
209
236
  """
210
237
  if not self.system_data:
211
238
  return {}
212
-
239
+
213
240
  # 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
-
241
+ cpu_values = [d["cpu_percent"] for d in self.system_data]
242
+ memory_values = [d["memory_percent"] for d in self.system_data]
243
+ disk_values = [d["disk_percent"] for d in self.system_data]
244
+
219
245
  # Calculate statistics
220
246
  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,
247
+ "start_time": self.start_time,
248
+ "end_time": self.end_time,
249
+ "duration": self.end_time - self.start_time
250
+ if self.end_time
251
+ else None,
252
+ "sample_count": len(self.system_data),
253
+ "cpu_min": min(cpu_values) if cpu_values else None,
254
+ "cpu_max": max(cpu_values) if cpu_values else None,
255
+ "cpu_avg": sum(cpu_values) / len(cpu_values)
256
+ if cpu_values
257
+ else None,
258
+ "memory_min_percent": min(memory_values) if memory_values else None,
259
+ "memory_max_percent": max(memory_values) if memory_values else None,
260
+ "memory_avg_percent": (
261
+ sum(memory_values) / len(memory_values)
262
+ if memory_values
263
+ else None
264
+ ),
265
+ "disk_min_percent": min(disk_values) if disk_values else None,
266
+ "disk_max_percent": max(disk_values) if disk_values else None,
267
+ "disk_avg_percent": (
268
+ sum(disk_values) / len(disk_values) if disk_values else None
269
+ ),
270
+ "memory_total_gb": (
271
+ self.system_data[0]["memory_total"] / (1024**3)
272
+ if self.system_data
273
+ else None
274
+ ),
275
+ "disk_total_gb": (
276
+ self.system_data[0]["disk_total"] / (1024**3)
277
+ if self.system_data
278
+ else None
279
+ ),
236
280
  }
237
-
281
+
238
282
  return stats
239
-
283
+
240
284
  def get_combined_stats(self) -> Dict[str, Any]:
241
285
  """
242
286
  Get combined resource usage statistics.
243
-
287
+
244
288
  Returns:
245
289
  Dictionary with both process and system statistics
246
290
  """
247
291
  process_stats = self.get_process_stats()
248
292
  system_stats = self.get_system_stats()
249
-
293
+
250
294
  # Combine stats
251
295
  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,
296
+ "start_time": self.start_time,
297
+ "end_time": self.end_time,
298
+ "duration": self.end_time - self.start_time
299
+ if self.end_time
300
+ else None,
255
301
  }
256
-
302
+
257
303
  # Add process stats with 'process_' prefix
258
304
  for key, value in process_stats.items():
259
- if key not in ['start_time', 'end_time', 'duration']:
260
- stats[f'process_{key}'] = value
261
-
305
+ if key not in ["start_time", "end_time", "duration"]:
306
+ stats[f"process_{key}"] = value
307
+
262
308
  # Add system stats with 'system_' prefix
263
309
  for key, value in system_stats.items():
264
- if key not in ['start_time', 'end_time', 'duration']:
265
- stats[f'system_{key}'] = value
266
-
310
+ if key not in ["start_time", "end_time", "duration"]:
311
+ stats[f"system_{key}"] = value
312
+
267
313
  # 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
-
314
+ if (
315
+ process_stats.get("memory_max_mb") is not None
316
+ and system_stats.get("memory_total_gb") is not None
317
+ ):
271
318
  # 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
319
+ system_memory_mb = system_stats["memory_total_gb"] * 1024
320
+ stats["process_memory_percent"] = (
321
+ (process_stats["memory_max_mb"] / system_memory_mb) * 100
322
+ if system_memory_mb > 0
323
+ else 0
276
324
  )
277
-
325
+
278
326
  return stats
279
-
327
+
280
328
  def print_summary(self):
281
329
  """Print a formatted summary of resource usage."""
282
330
  process_stats = self.get_process_stats()
283
331
  system_stats = self.get_system_stats()
284
-
332
+
285
333
  print("\n===== RESOURCE USAGE SUMMARY =====")
286
-
334
+
287
335
  if process_stats:
288
336
  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")
337
+ print(
338
+ f"CPU usage: {process_stats.get('cpu_avg', 0):.1f}% avg, "
339
+ f"{process_stats.get('cpu_max', 0):.1f}% peak"
340
+ )
341
+ print(
342
+ f"Memory usage: {process_stats.get('memory_avg_mb', 0):.1f} MB avg, "
343
+ f"{process_stats.get('memory_max_mb', 0):.1f} MB peak"
344
+ )
293
345
  print(f"Threads: {process_stats.get('thread_max', 0)} max")
294
-
346
+
295
347
  if system_stats:
296
348
  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
-
349
+ print(
350
+ f"CPU usage: {system_stats.get('cpu_avg', 0):.1f}% avg, "
351
+ f"{system_stats.get('cpu_max', 0):.1f}% peak"
352
+ )
353
+ print(
354
+ f"Memory usage: {system_stats.get('memory_avg_percent', 0):.1f}% avg, "
355
+ f"{system_stats.get('memory_max_percent', 0):.1f}% peak "
356
+ f"(Total: {system_stats.get('memory_total_gb', 0):.1f} GB)"
357
+ )
358
+ print(
359
+ f"Disk usage: {system_stats.get('disk_avg_percent', 0):.1f}% avg "
360
+ f"(Total: {system_stats.get('disk_total_gb', 0):.1f} GB)"
361
+ )
362
+
305
363
  print("\n===================================")
306
-
364
+
307
365
  def export_data(self) -> Dict[str, Any]:
308
366
  """
309
367
  Export all collected data.
310
-
368
+
311
369
  Returns:
312
370
  Dictionary with all collected resource usage data
313
371
  """
314
372
  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
373
+ "start_time": self.start_time,
374
+ "end_time": self.end_time,
375
+ "sampling_interval": self.sampling_interval,
376
+ "process_data": self.process_data,
377
+ "system_data": self.system_data,
320
378
  }
321
379
 
322
380
 
323
381
  def check_system_resources() -> Dict[str, Any]:
324
382
  """
325
383
  Check current system resources.
326
-
384
+
327
385
  Returns:
328
386
  Dictionary with current resource usage information
329
387
  """
330
388
  if not PSUTIL_AVAILABLE:
331
- return {
332
- 'error': 'psutil not available',
333
- 'available': False
334
- }
335
-
389
+ return {"error": "psutil not available", "available": False}
390
+
336
391
  try:
337
392
  # Get basic system information
338
393
  cpu_count = psutil.cpu_count(logical=True)
339
394
  cpu_physical = psutil.cpu_count(logical=False)
340
395
  cpu_percent = psutil.cpu_percent(interval=0.1)
341
-
396
+
342
397
  memory = psutil.virtual_memory()
343
- disk = psutil.disk_usage('/')
344
-
398
+ disk = psutil.disk_usage("/")
399
+
345
400
  # Format results
346
401
  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
402
+ "available": True,
403
+ "cpu_count": cpu_count,
404
+ "cpu_physical": cpu_physical,
405
+ "cpu_percent": cpu_percent,
406
+ "memory_total_gb": memory.total / (1024**3),
407
+ "memory_available_gb": memory.available / (1024**3),
408
+ "memory_used_gb": memory.used / (1024**3),
409
+ "memory_percent": memory.percent,
410
+ "disk_total_gb": disk.total / (1024**3),
411
+ "disk_free_gb": disk.free / (1024**3),
412
+ "disk_percent": disk.percent,
358
413
  }
359
-
414
+
360
415
  return result
361
-
416
+
362
417
  except Exception as e:
363
418
  logger.error(f"Error checking system resources: {str(e)}")
364
- return {
365
- 'error': str(e),
366
- 'available': False
367
- }
419
+ return {"error": str(e), "available": False}