kailash 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.
Files changed (69) hide show
  1. kailash/__init__.py +31 -0
  2. kailash/__main__.py +11 -0
  3. kailash/cli/__init__.py +5 -0
  4. kailash/cli/commands.py +563 -0
  5. kailash/manifest.py +778 -0
  6. kailash/nodes/__init__.py +23 -0
  7. kailash/nodes/ai/__init__.py +26 -0
  8. kailash/nodes/ai/agents.py +417 -0
  9. kailash/nodes/ai/models.py +488 -0
  10. kailash/nodes/api/__init__.py +52 -0
  11. kailash/nodes/api/auth.py +567 -0
  12. kailash/nodes/api/graphql.py +480 -0
  13. kailash/nodes/api/http.py +598 -0
  14. kailash/nodes/api/rate_limiting.py +572 -0
  15. kailash/nodes/api/rest.py +665 -0
  16. kailash/nodes/base.py +1032 -0
  17. kailash/nodes/base_async.py +128 -0
  18. kailash/nodes/code/__init__.py +32 -0
  19. kailash/nodes/code/python.py +1021 -0
  20. kailash/nodes/data/__init__.py +125 -0
  21. kailash/nodes/data/readers.py +496 -0
  22. kailash/nodes/data/sharepoint_graph.py +623 -0
  23. kailash/nodes/data/sql.py +380 -0
  24. kailash/nodes/data/streaming.py +1168 -0
  25. kailash/nodes/data/vector_db.py +964 -0
  26. kailash/nodes/data/writers.py +529 -0
  27. kailash/nodes/logic/__init__.py +6 -0
  28. kailash/nodes/logic/async_operations.py +702 -0
  29. kailash/nodes/logic/operations.py +551 -0
  30. kailash/nodes/transform/__init__.py +5 -0
  31. kailash/nodes/transform/processors.py +379 -0
  32. kailash/runtime/__init__.py +6 -0
  33. kailash/runtime/async_local.py +356 -0
  34. kailash/runtime/docker.py +697 -0
  35. kailash/runtime/local.py +434 -0
  36. kailash/runtime/parallel.py +557 -0
  37. kailash/runtime/runner.py +110 -0
  38. kailash/runtime/testing.py +347 -0
  39. kailash/sdk_exceptions.py +307 -0
  40. kailash/tracking/__init__.py +7 -0
  41. kailash/tracking/manager.py +885 -0
  42. kailash/tracking/metrics_collector.py +342 -0
  43. kailash/tracking/models.py +535 -0
  44. kailash/tracking/storage/__init__.py +0 -0
  45. kailash/tracking/storage/base.py +113 -0
  46. kailash/tracking/storage/database.py +619 -0
  47. kailash/tracking/storage/filesystem.py +543 -0
  48. kailash/utils/__init__.py +0 -0
  49. kailash/utils/export.py +924 -0
  50. kailash/utils/templates.py +680 -0
  51. kailash/visualization/__init__.py +62 -0
  52. kailash/visualization/api.py +732 -0
  53. kailash/visualization/dashboard.py +951 -0
  54. kailash/visualization/performance.py +808 -0
  55. kailash/visualization/reports.py +1471 -0
  56. kailash/workflow/__init__.py +15 -0
  57. kailash/workflow/builder.py +245 -0
  58. kailash/workflow/graph.py +827 -0
  59. kailash/workflow/mermaid_visualizer.py +628 -0
  60. kailash/workflow/mock_registry.py +63 -0
  61. kailash/workflow/runner.py +302 -0
  62. kailash/workflow/state.py +238 -0
  63. kailash/workflow/visualization.py +588 -0
  64. kailash-0.1.0.dist-info/METADATA +710 -0
  65. kailash-0.1.0.dist-info/RECORD +69 -0
  66. kailash-0.1.0.dist-info/WHEEL +5 -0
  67. kailash-0.1.0.dist-info/entry_points.txt +2 -0
  68. kailash-0.1.0.dist-info/licenses/LICENSE +21 -0
  69. kailash-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,342 @@
1
+ """Enhanced metrics collection for task tracking.
2
+
3
+ This module provides comprehensive performance metrics collection during node execution,
4
+ including CPU usage, memory consumption, I/O operations, and custom metrics.
5
+
6
+ Design Purpose:
7
+ - Enable real-time performance monitoring during node execution
8
+ - Integrate seamlessly with TaskManager and visualization components
9
+ - Support both synchronous and asynchronous execution contexts
10
+
11
+ Upstream Dependencies:
12
+ - Runtime engines (local.py, parallel.py, docker.py) use this to collect metrics
13
+ - TaskManager uses this to store performance data
14
+
15
+ Downstream Consumers:
16
+ - Visualization components use collected metrics for performance graphs
17
+ - Export utilities include metrics in workflow reports
18
+ """
19
+
20
+ import asyncio
21
+ import threading
22
+ import time
23
+ from contextlib import contextmanager
24
+ from dataclasses import dataclass, field
25
+ from typing import Any, Callable, Dict, Optional
26
+
27
+ try:
28
+ import psutil
29
+
30
+ PSUTIL_AVAILABLE = True
31
+ except ImportError:
32
+ PSUTIL_AVAILABLE = False
33
+
34
+
35
+ @dataclass
36
+ class PerformanceMetrics:
37
+ """Container for comprehensive performance metrics.
38
+
39
+ Attributes:
40
+ duration: Execution time in seconds
41
+ cpu_percent: Average CPU usage percentage
42
+ memory_mb: Peak memory usage in MB
43
+ memory_delta_mb: Memory increase during execution
44
+ io_read_bytes: Total bytes read during execution
45
+ io_write_bytes: Total bytes written during execution
46
+ io_read_count: Number of read operations
47
+ io_write_count: Number of write operations
48
+ thread_count: Number of threads used
49
+ context_switches: Number of context switches
50
+ custom: Dictionary of custom metrics
51
+ """
52
+
53
+ duration: float = 0.0
54
+ cpu_percent: float = 0.0
55
+ memory_mb: float = 0.0
56
+ memory_delta_mb: float = 0.0
57
+ io_read_bytes: int = 0
58
+ io_write_bytes: int = 0
59
+ io_read_count: int = 0
60
+ io_write_count: int = 0
61
+ thread_count: int = 1
62
+ context_switches: int = 0
63
+ custom: Dict[str, Any] = field(default_factory=dict)
64
+
65
+ def to_task_metrics(self) -> Dict[str, Any]:
66
+ """Convert to TaskMetrics compatible format."""
67
+ return {
68
+ "duration": self.duration,
69
+ "memory_usage_mb": self.memory_mb,
70
+ "cpu_usage": self.cpu_percent,
71
+ "custom_metrics": {
72
+ "memory_delta_mb": self.memory_delta_mb,
73
+ "io_read_bytes": self.io_read_bytes,
74
+ "io_write_bytes": self.io_write_bytes,
75
+ "io_read_count": self.io_read_count,
76
+ "io_write_count": self.io_write_count,
77
+ "thread_count": self.thread_count,
78
+ "context_switches": self.context_switches,
79
+ **self.custom,
80
+ },
81
+ }
82
+
83
+
84
+ class MetricsCollector:
85
+ """Collects performance metrics during task execution.
86
+
87
+ This class provides context managers for collecting detailed performance
88
+ metrics during node execution, with support for both process-level and
89
+ system-level monitoring.
90
+
91
+ Usage:
92
+ collector = MetricsCollector()
93
+ with collector.collect() as metrics:
94
+ # Execute node code here
95
+ pass
96
+ performance_data = metrics.result()
97
+ """
98
+
99
+ def __init__(self, sampling_interval: float = 0.1):
100
+ """Initialize metrics collector.
101
+
102
+ Args:
103
+ sampling_interval: How often to sample metrics (seconds)
104
+ """
105
+ self.sampling_interval = sampling_interval
106
+ self._monitoring_enabled = PSUTIL_AVAILABLE
107
+
108
+ if not self._monitoring_enabled:
109
+ import warnings
110
+
111
+ warnings.warn(
112
+ "psutil not available. Performance metrics will be limited to duration only. "
113
+ "Install psutil for comprehensive metrics: pip install psutil"
114
+ )
115
+
116
+ @contextmanager
117
+ def collect(self, node_id: Optional[str] = None):
118
+ """Context manager for collecting metrics during execution.
119
+
120
+ Args:
121
+ node_id: Optional node identifier for tracking
122
+
123
+ Yields:
124
+ MetricsContext: Context object with result() method
125
+ """
126
+ context = MetricsContext(
127
+ node_id=node_id,
128
+ sampling_interval=self.sampling_interval,
129
+ monitoring_enabled=self._monitoring_enabled,
130
+ )
131
+
132
+ try:
133
+ context.start()
134
+ yield context
135
+ finally:
136
+ context.stop()
137
+
138
+ async def collect_async(self, coro, node_id: Optional[str] = None):
139
+ """Collect metrics for async execution.
140
+
141
+ Args:
142
+ coro: Coroutine to execute
143
+ node_id: Optional node identifier
144
+
145
+ Returns:
146
+ Tuple of (result, metrics)
147
+ """
148
+ context = MetricsContext(
149
+ node_id=node_id,
150
+ sampling_interval=self.sampling_interval,
151
+ monitoring_enabled=self._monitoring_enabled,
152
+ )
153
+
154
+ try:
155
+ context.start()
156
+ result = await coro
157
+ return result, context.result()
158
+ finally:
159
+ context.stop()
160
+
161
+
162
+ class MetricsContext:
163
+ """Context for collecting metrics during a specific execution."""
164
+
165
+ def __init__(
166
+ self, node_id: Optional[str], sampling_interval: float, monitoring_enabled: bool
167
+ ):
168
+ self.node_id = node_id
169
+ self.sampling_interval = sampling_interval
170
+ self.monitoring_enabled = monitoring_enabled
171
+
172
+ self.start_time: Optional[float] = None
173
+ self.end_time: Optional[float] = None
174
+ self.process: Optional[Any] = None
175
+ self.initial_io: Optional[Any] = None
176
+ self.initial_memory: Optional[float] = None
177
+ self.peak_memory: float = 0.0
178
+ self.cpu_samples: list = []
179
+ self.monitoring_thread: Optional[threading.Thread] = None
180
+ self._stop_monitoring = threading.Event()
181
+
182
+ def start(self):
183
+ """Start metrics collection."""
184
+ self.start_time = time.time()
185
+
186
+ if self.monitoring_enabled:
187
+ try:
188
+ self.process = psutil.Process()
189
+ self.initial_memory = self.process.memory_info().rss / 1024 / 1024 # MB
190
+ self.peak_memory = self.initial_memory
191
+
192
+ # Get initial I/O counters if available
193
+ if hasattr(self.process, "io_counters"):
194
+ try:
195
+ self.initial_io = self.process.io_counters()
196
+ except (psutil.AccessDenied, AttributeError):
197
+ self.initial_io = None
198
+
199
+ # Start monitoring thread
200
+ self._stop_monitoring.clear()
201
+ self.monitoring_thread = threading.Thread(
202
+ target=self._monitor_resources
203
+ )
204
+ self.monitoring_thread.daemon = True
205
+ self.monitoring_thread.start()
206
+
207
+ except Exception:
208
+ # Fallback if process monitoring fails
209
+ self.monitoring_enabled = False
210
+
211
+ def stop(self):
212
+ """Stop metrics collection."""
213
+ self.end_time = time.time()
214
+
215
+ if self.monitoring_enabled and self.monitoring_thread:
216
+ self._stop_monitoring.set()
217
+ self.monitoring_thread.join(timeout=1.0)
218
+
219
+ def _monitor_resources(self):
220
+ """Monitor resources in background thread."""
221
+ while not self._stop_monitoring.is_set():
222
+ try:
223
+ # Sample CPU usage
224
+ cpu = self.process.cpu_percent(interval=None)
225
+ if cpu > 0: # Filter out initial 0 readings
226
+ self.cpu_samples.append(cpu)
227
+
228
+ # Track peak memory
229
+ memory = self.process.memory_info().rss / 1024 / 1024 # MB
230
+ self.peak_memory = max(self.peak_memory, memory)
231
+
232
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
233
+ break
234
+
235
+ self._stop_monitoring.wait(self.sampling_interval)
236
+
237
+ def result(self) -> PerformanceMetrics:
238
+ """Get collected metrics."""
239
+ metrics = PerformanceMetrics()
240
+
241
+ # Calculate duration
242
+ if self.start_time and self.end_time:
243
+ metrics.duration = self.end_time - self.start_time
244
+
245
+ if self.monitoring_enabled and self.process:
246
+ try:
247
+ # CPU usage (average of samples)
248
+ if self.cpu_samples:
249
+ metrics.cpu_percent = sum(self.cpu_samples) / len(self.cpu_samples)
250
+
251
+ # Memory metrics
252
+ metrics.memory_mb = self.peak_memory
253
+ if self.initial_memory:
254
+ current_memory = self.process.memory_info().rss / 1024 / 1024
255
+ metrics.memory_delta_mb = current_memory - self.initial_memory
256
+
257
+ # I/O metrics
258
+ if self.initial_io and hasattr(self.process, "io_counters"):
259
+ try:
260
+ current_io = self.process.io_counters()
261
+ metrics.io_read_bytes = (
262
+ current_io.read_bytes - self.initial_io.read_bytes
263
+ )
264
+ metrics.io_write_bytes = (
265
+ current_io.write_bytes - self.initial_io.write_bytes
266
+ )
267
+ metrics.io_read_count = (
268
+ current_io.read_count - self.initial_io.read_count
269
+ )
270
+ metrics.io_write_count = (
271
+ current_io.write_count - self.initial_io.write_count
272
+ )
273
+ except (psutil.AccessDenied, AttributeError):
274
+ pass
275
+
276
+ # Thread and context switch info
277
+ try:
278
+ metrics.thread_count = self.process.num_threads()
279
+ if hasattr(self.process, "num_ctx_switches"):
280
+ ctx = self.process.num_ctx_switches()
281
+ metrics.context_switches = ctx.voluntary + ctx.involuntary
282
+ except (psutil.AccessDenied, AttributeError):
283
+ pass
284
+
285
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
286
+ pass
287
+
288
+ return metrics
289
+
290
+ def add_custom_metric(self, name: str, value: Any):
291
+ """Add a custom metric."""
292
+ if not hasattr(self, "_custom_metrics"):
293
+ self._custom_metrics = {}
294
+ self._custom_metrics[name] = value
295
+
296
+ def get_custom_metrics(self) -> Dict[str, Any]:
297
+ """Get custom metrics."""
298
+ return getattr(self, "_custom_metrics", {})
299
+
300
+
301
+ # Global collector instance for convenience
302
+ default_collector = MetricsCollector()
303
+
304
+
305
+ def collect_metrics(func: Optional[Callable] = None, *, node_id: Optional[str] = None):
306
+ """Decorator for collecting metrics on function execution.
307
+
308
+ Can be used as @collect_metrics or @collect_metrics(node_id="my_node")
309
+
310
+ Args:
311
+ func: Function to wrap
312
+ node_id: Optional node identifier
313
+
314
+ Returns:
315
+ Wrapped function that returns (result, metrics) tuple
316
+ """
317
+
318
+ def decorator(f):
319
+ if asyncio.iscoroutinefunction(f):
320
+
321
+ async def async_wrapper(*args, **kwargs):
322
+ result, metrics = await default_collector.collect_async(
323
+ f(*args, **kwargs), node_id=node_id
324
+ )
325
+ return result, metrics
326
+
327
+ return async_wrapper
328
+ else:
329
+
330
+ def sync_wrapper(*args, **kwargs):
331
+ with default_collector.collect(node_id=node_id) as context:
332
+ result = f(*args, **kwargs)
333
+ return result, context.result()
334
+
335
+ return sync_wrapper
336
+
337
+ if func is None:
338
+ # Called with arguments: @collect_metrics(node_id="...")
339
+ return decorator
340
+ else:
341
+ # Called without arguments: @collect_metrics
342
+ return decorator(func)