kailash 0.2.2__py3-none-any.whl → 0.3.1__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.
- kailash/__init__.py +1 -1
- kailash/access_control.py +40 -39
- kailash/api/auth.py +26 -32
- kailash/api/custom_nodes.py +29 -29
- kailash/api/custom_nodes_secure.py +35 -35
- kailash/api/database.py +17 -17
- kailash/api/gateway.py +19 -19
- kailash/api/mcp_integration.py +24 -23
- kailash/api/studio.py +45 -45
- kailash/api/workflow_api.py +8 -8
- kailash/cli/commands.py +5 -8
- kailash/manifest.py +42 -42
- kailash/mcp/__init__.py +1 -1
- kailash/mcp/ai_registry_server.py +20 -20
- kailash/mcp/client.py +9 -11
- kailash/mcp/client_new.py +10 -10
- kailash/mcp/server.py +1 -2
- kailash/mcp/server_enhanced.py +449 -0
- kailash/mcp/servers/ai_registry.py +6 -6
- kailash/mcp/utils/__init__.py +31 -0
- kailash/mcp/utils/cache.py +267 -0
- kailash/mcp/utils/config.py +263 -0
- kailash/mcp/utils/formatters.py +293 -0
- kailash/mcp/utils/metrics.py +418 -0
- kailash/nodes/ai/agents.py +9 -9
- kailash/nodes/ai/ai_providers.py +33 -34
- kailash/nodes/ai/embedding_generator.py +31 -32
- kailash/nodes/ai/intelligent_agent_orchestrator.py +62 -66
- kailash/nodes/ai/iterative_llm_agent.py +48 -48
- kailash/nodes/ai/llm_agent.py +32 -33
- kailash/nodes/ai/models.py +13 -13
- kailash/nodes/ai/self_organizing.py +44 -44
- kailash/nodes/api/__init__.py +5 -0
- kailash/nodes/api/auth.py +11 -11
- kailash/nodes/api/graphql.py +13 -13
- kailash/nodes/api/http.py +19 -19
- kailash/nodes/api/monitoring.py +463 -0
- kailash/nodes/api/rate_limiting.py +9 -13
- kailash/nodes/api/rest.py +29 -29
- kailash/nodes/api/security.py +819 -0
- kailash/nodes/base.py +24 -26
- kailash/nodes/base_async.py +7 -7
- kailash/nodes/base_cycle_aware.py +12 -12
- kailash/nodes/base_with_acl.py +5 -5
- kailash/nodes/code/python.py +56 -55
- kailash/nodes/data/__init__.py +6 -0
- kailash/nodes/data/directory.py +6 -6
- kailash/nodes/data/event_generation.py +297 -0
- kailash/nodes/data/file_discovery.py +598 -0
- kailash/nodes/data/readers.py +8 -8
- kailash/nodes/data/retrieval.py +10 -10
- kailash/nodes/data/sharepoint_graph.py +17 -17
- kailash/nodes/data/sources.py +5 -5
- kailash/nodes/data/sql.py +13 -13
- kailash/nodes/data/streaming.py +25 -25
- kailash/nodes/data/vector_db.py +22 -22
- kailash/nodes/data/writers.py +7 -7
- kailash/nodes/logic/async_operations.py +17 -17
- kailash/nodes/logic/convergence.py +11 -11
- kailash/nodes/logic/loop.py +4 -4
- kailash/nodes/logic/operations.py +11 -11
- kailash/nodes/logic/workflow.py +8 -9
- kailash/nodes/mixins/mcp.py +17 -17
- kailash/nodes/mixins.py +8 -10
- kailash/nodes/transform/chunkers.py +3 -3
- kailash/nodes/transform/formatters.py +7 -7
- kailash/nodes/transform/processors.py +11 -11
- kailash/runtime/access_controlled.py +18 -18
- kailash/runtime/async_local.py +18 -20
- kailash/runtime/docker.py +24 -26
- kailash/runtime/local.py +55 -31
- kailash/runtime/parallel.py +25 -25
- kailash/runtime/parallel_cyclic.py +29 -29
- kailash/runtime/runner.py +6 -6
- kailash/runtime/testing.py +22 -22
- kailash/sdk_exceptions.py +0 -58
- kailash/security.py +14 -26
- kailash/tracking/manager.py +38 -38
- kailash/tracking/metrics_collector.py +15 -14
- kailash/tracking/models.py +53 -53
- kailash/tracking/storage/base.py +7 -17
- kailash/tracking/storage/database.py +22 -23
- kailash/tracking/storage/filesystem.py +38 -40
- kailash/utils/export.py +21 -21
- kailash/utils/templates.py +8 -9
- kailash/visualization/api.py +30 -34
- kailash/visualization/dashboard.py +17 -17
- kailash/visualization/performance.py +32 -19
- kailash/visualization/reports.py +30 -28
- kailash/workflow/builder.py +8 -8
- kailash/workflow/convergence.py +13 -12
- kailash/workflow/cycle_analyzer.py +38 -33
- kailash/workflow/cycle_builder.py +12 -12
- kailash/workflow/cycle_config.py +16 -15
- kailash/workflow/cycle_debugger.py +40 -40
- kailash/workflow/cycle_exceptions.py +29 -29
- kailash/workflow/cycle_profiler.py +21 -21
- kailash/workflow/cycle_state.py +20 -22
- kailash/workflow/cyclic_runner.py +45 -45
- kailash/workflow/graph.py +57 -45
- kailash/workflow/mermaid_visualizer.py +9 -11
- kailash/workflow/migration.py +22 -22
- kailash/workflow/mock_registry.py +6 -6
- kailash/workflow/runner.py +9 -9
- kailash/workflow/safety.py +12 -13
- kailash/workflow/state.py +8 -11
- kailash/workflow/templates.py +19 -19
- kailash/workflow/validation.py +14 -14
- kailash/workflow/visualization.py +32 -24
- kailash-0.3.1.dist-info/METADATA +476 -0
- kailash-0.3.1.dist-info/RECORD +136 -0
- kailash-0.2.2.dist-info/METADATA +0 -121
- kailash-0.2.2.dist-info/RECORD +0 -126
- {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/WHEEL +0 -0
- {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/top_level.txt +0 -0
kailash/visualization/api.py
CHANGED
@@ -27,7 +27,7 @@ import json
|
|
27
27
|
import logging
|
28
28
|
from datetime import datetime
|
29
29
|
from pathlib import Path
|
30
|
-
from typing import Any,
|
30
|
+
from typing import Any, Optional
|
31
31
|
|
32
32
|
try:
|
33
33
|
from fastapi import (
|
@@ -59,8 +59,8 @@ if FASTAPI_AVAILABLE:
|
|
59
59
|
class RunRequest(BaseModel):
|
60
60
|
"""Request model for starting monitoring."""
|
61
61
|
|
62
|
-
run_id:
|
63
|
-
config:
|
62
|
+
run_id: str | None = None
|
63
|
+
config: dict[str, Any] | None = None
|
64
64
|
|
65
65
|
class MetricsResponse(BaseModel):
|
66
66
|
"""Response model for metrics data."""
|
@@ -80,12 +80,12 @@ if FASTAPI_AVAILABLE:
|
|
80
80
|
node_id: str
|
81
81
|
node_type: str
|
82
82
|
status: str
|
83
|
-
started_at:
|
84
|
-
ended_at:
|
85
|
-
duration:
|
86
|
-
cpu_usage:
|
87
|
-
memory_usage_mb:
|
88
|
-
error_message:
|
83
|
+
started_at: datetime | None
|
84
|
+
ended_at: datetime | None
|
85
|
+
duration: float | None
|
86
|
+
cpu_usage: float | None
|
87
|
+
memory_usage_mb: float | None
|
88
|
+
error_message: str | None
|
89
89
|
|
90
90
|
class RunResponse(BaseModel):
|
91
91
|
"""Response model for run information."""
|
@@ -93,8 +93,8 @@ if FASTAPI_AVAILABLE:
|
|
93
93
|
run_id: str
|
94
94
|
workflow_name: str
|
95
95
|
status: str
|
96
|
-
started_at:
|
97
|
-
ended_at:
|
96
|
+
started_at: datetime | None
|
97
|
+
ended_at: datetime | None
|
98
98
|
total_tasks: int
|
99
99
|
completed_tasks: int
|
100
100
|
failed_tasks: int
|
@@ -105,7 +105,7 @@ if FASTAPI_AVAILABLE:
|
|
105
105
|
run_id: str
|
106
106
|
format: str = "html"
|
107
107
|
include_charts: bool = True
|
108
|
-
compare_runs:
|
108
|
+
compare_runs: list[str] | None = None
|
109
109
|
detail_level: str = "detailed"
|
110
110
|
|
111
111
|
|
@@ -123,7 +123,7 @@ class DashboardAPIServer:
|
|
123
123
|
def __init__(
|
124
124
|
self,
|
125
125
|
task_manager: TaskManager,
|
126
|
-
dashboard_config:
|
126
|
+
dashboard_config: DashboardConfig | None = None,
|
127
127
|
):
|
128
128
|
"""Initialize API server.
|
129
129
|
|
@@ -145,8 +145,8 @@ class DashboardAPIServer:
|
|
145
145
|
self.reporter = WorkflowPerformanceReporter(task_manager)
|
146
146
|
|
147
147
|
# WebSocket connections for real-time updates
|
148
|
-
self._websocket_connections:
|
149
|
-
self._broadcast_task:
|
148
|
+
self._websocket_connections: list[WebSocket] = []
|
149
|
+
self._broadcast_task: asyncio.Task | None = None
|
150
150
|
|
151
151
|
# Create FastAPI app
|
152
152
|
self.app = FastAPI(
|
@@ -177,7 +177,7 @@ class DashboardAPIServer:
|
|
177
177
|
"""Health check endpoint."""
|
178
178
|
return {"status": "healthy", "timestamp": datetime.now()}
|
179
179
|
|
180
|
-
@self.app.get("/api/v1/runs", response_model=
|
180
|
+
@self.app.get("/api/v1/runs", response_model=list[RunResponse])
|
181
181
|
async def list_runs(limit: int = 10, offset: int = 0):
|
182
182
|
"""Get list of workflow runs."""
|
183
183
|
try:
|
@@ -243,7 +243,7 @@ class DashboardAPIServer:
|
|
243
243
|
self.logger.error(f"Failed to get run {run_id}: {e}")
|
244
244
|
raise HTTPException(status_code=500, detail=str(e))
|
245
245
|
|
246
|
-
@self.app.get("/api/v1/runs/{run_id}/tasks", response_model=
|
246
|
+
@self.app.get("/api/v1/runs/{run_id}/tasks", response_model=list[TaskResponse])
|
247
247
|
async def get_run_tasks(run_id: str):
|
248
248
|
"""Get tasks for a specific run."""
|
249
249
|
try:
|
@@ -352,7 +352,7 @@ class DashboardAPIServer:
|
|
352
352
|
self.logger.error(f"Failed to get current metrics: {e}")
|
353
353
|
raise HTTPException(status_code=500, detail=str(e))
|
354
354
|
|
355
|
-
@self.app.get("/api/v1/metrics/history", response_model=
|
355
|
+
@self.app.get("/api/v1/metrics/history", response_model=list[MetricsResponse])
|
356
356
|
async def get_metrics_history(minutes: int = 30):
|
357
357
|
"""Get metrics history for specified time period."""
|
358
358
|
try:
|
@@ -475,7 +475,7 @@ class DashboardAPIServer:
|
|
475
475
|
run_id: str,
|
476
476
|
output_path: Path,
|
477
477
|
report_format: ReportFormat,
|
478
|
-
compare_runs:
|
478
|
+
compare_runs: list[str] | None = None,
|
479
479
|
):
|
480
480
|
"""Generate report in background task."""
|
481
481
|
try:
|
@@ -564,7 +564,7 @@ class SimpleDashboardAPI:
|
|
564
564
|
def __init__(
|
565
565
|
self,
|
566
566
|
task_manager: TaskManager,
|
567
|
-
dashboard_config:
|
567
|
+
dashboard_config: DashboardConfig | None = None,
|
568
568
|
):
|
569
569
|
"""Initialize simple API interface.
|
570
570
|
|
@@ -578,7 +578,7 @@ class SimpleDashboardAPI:
|
|
578
578
|
self.reporter = WorkflowPerformanceReporter(task_manager)
|
579
579
|
self.logger = logger
|
580
580
|
|
581
|
-
def get_runs(self, limit: int = 10, offset: int = 0) ->
|
581
|
+
def get_runs(self, limit: int = 10, offset: int = 0) -> list[dict[str, Any]]:
|
582
582
|
"""Get list of workflow runs."""
|
583
583
|
all_runs = self.task_manager.list_runs()
|
584
584
|
runs = all_runs[offset : offset + limit]
|
@@ -604,7 +604,7 @@ class SimpleDashboardAPI:
|
|
604
604
|
|
605
605
|
return result
|
606
606
|
|
607
|
-
def get_run_details(self, run_id: str) ->
|
607
|
+
def get_run_details(self, run_id: str) -> dict[str, Any] | None:
|
608
608
|
"""Get details for a specific run."""
|
609
609
|
run = self.task_manager.get_run(run_id)
|
610
610
|
if not run:
|
@@ -641,17 +641,17 @@ class SimpleDashboardAPI:
|
|
641
641
|
],
|
642
642
|
}
|
643
643
|
|
644
|
-
def start_monitoring(self, run_id:
|
644
|
+
def start_monitoring(self, run_id: str | None = None) -> dict[str, Any]:
|
645
645
|
"""Start real-time monitoring."""
|
646
646
|
self.dashboard.start_monitoring(run_id)
|
647
647
|
return {"status": "started", "run_id": run_id}
|
648
648
|
|
649
|
-
def stop_monitoring(self) ->
|
649
|
+
def stop_monitoring(self) -> dict[str, Any]:
|
650
650
|
"""Stop real-time monitoring."""
|
651
651
|
self.dashboard.stop_monitoring()
|
652
652
|
return {"status": "stopped"}
|
653
653
|
|
654
|
-
def get_current_metrics(self) ->
|
654
|
+
def get_current_metrics(self) -> dict[str, Any] | None:
|
655
655
|
"""Get current live metrics."""
|
656
656
|
metrics = self.dashboard.get_current_metrics()
|
657
657
|
if not metrics:
|
@@ -668,7 +668,7 @@ class SimpleDashboardAPI:
|
|
668
668
|
"avg_task_duration": metrics.avg_task_duration,
|
669
669
|
}
|
670
670
|
|
671
|
-
def get_metrics_history(self, minutes: int = 30) ->
|
671
|
+
def get_metrics_history(self, minutes: int = 30) -> list[dict[str, Any]]:
|
672
672
|
"""Get metrics history."""
|
673
673
|
history = self.dashboard.get_metrics_history(minutes=minutes)
|
674
674
|
|
@@ -690,8 +690,8 @@ class SimpleDashboardAPI:
|
|
690
690
|
self,
|
691
691
|
run_id: str,
|
692
692
|
format: str = "html",
|
693
|
-
output_path:
|
694
|
-
compare_runs:
|
693
|
+
output_path: str | Path | None = None,
|
694
|
+
compare_runs: list[str] | None = None,
|
695
695
|
) -> Path:
|
696
696
|
"""Generate performance report."""
|
697
697
|
try:
|
@@ -708,9 +708,7 @@ class SimpleDashboardAPI:
|
|
708
708
|
compare_runs=compare_runs,
|
709
709
|
)
|
710
710
|
|
711
|
-
def generate_dashboard(
|
712
|
-
self, output_path: Optional[Union[str, Path]] = None
|
713
|
-
) -> Path:
|
711
|
+
def generate_dashboard(self, output_path: str | Path | None = None) -> Path:
|
714
712
|
"""Generate live dashboard HTML."""
|
715
713
|
if output_path is None:
|
716
714
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
@@ -718,9 +716,7 @@ class SimpleDashboardAPI:
|
|
718
716
|
|
719
717
|
return self.dashboard.generate_live_report(output_path, include_charts=True)
|
720
718
|
|
721
|
-
def export_metrics_json(
|
722
|
-
self, output_path: Optional[Union[str, Path]] = None
|
723
|
-
) -> Path:
|
719
|
+
def export_metrics_json(self, output_path: str | Path | None = None) -> Path:
|
724
720
|
"""Export current metrics as JSON."""
|
725
721
|
if output_path is None:
|
726
722
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
@@ -27,7 +27,7 @@ import time
|
|
27
27
|
from dataclasses import dataclass, field
|
28
28
|
from datetime import datetime, timedelta
|
29
29
|
from pathlib import Path
|
30
|
-
from typing import Any
|
30
|
+
from typing import Any
|
31
31
|
|
32
32
|
import numpy as np
|
33
33
|
|
@@ -100,7 +100,7 @@ class RealTimeDashboard:
|
|
100
100
|
"""
|
101
101
|
|
102
102
|
def __init__(
|
103
|
-
self, task_manager: TaskManager, config:
|
103
|
+
self, task_manager: TaskManager, config: DashboardConfig | None = None
|
104
104
|
):
|
105
105
|
"""Initialize real-time dashboard.
|
106
106
|
|
@@ -114,17 +114,17 @@ class RealTimeDashboard:
|
|
114
114
|
|
115
115
|
# Live monitoring state
|
116
116
|
self._monitoring = False
|
117
|
-
self._monitor_thread:
|
118
|
-
self._metrics_history:
|
119
|
-
self._current_run_id:
|
117
|
+
self._monitor_thread: threading.Thread | None = None
|
118
|
+
self._metrics_history: list[LiveMetrics] = []
|
119
|
+
self._current_run_id: str | None = None
|
120
120
|
|
121
121
|
# Event callbacks
|
122
|
-
self._status_callbacks:
|
123
|
-
self._metrics_callbacks:
|
122
|
+
self._status_callbacks: list[callable] = []
|
123
|
+
self._metrics_callbacks: list[callable] = []
|
124
124
|
|
125
125
|
self.logger = logger
|
126
126
|
|
127
|
-
def start_monitoring(self, run_id:
|
127
|
+
def start_monitoring(self, run_id: str | None = None):
|
128
128
|
"""Start real-time monitoring for a workflow run.
|
129
129
|
|
130
130
|
Args:
|
@@ -297,11 +297,11 @@ class RealTimeDashboard:
|
|
297
297
|
"""
|
298
298
|
self._status_callbacks.append(callback)
|
299
299
|
|
300
|
-
def get_current_metrics(self) ->
|
300
|
+
def get_current_metrics(self) -> LiveMetrics | None:
|
301
301
|
"""Get the most recent metrics."""
|
302
302
|
return self._metrics_history[-1] if self._metrics_history else None
|
303
303
|
|
304
|
-
def get_metrics_history(self, minutes:
|
304
|
+
def get_metrics_history(self, minutes: int | None = None) -> list[LiveMetrics]:
|
305
305
|
"""Get metrics history for specified time period.
|
306
306
|
|
307
307
|
Args:
|
@@ -317,7 +317,7 @@ class RealTimeDashboard:
|
|
317
317
|
return [m for m in self._metrics_history if m.timestamp >= cutoff]
|
318
318
|
|
319
319
|
def generate_live_report(
|
320
|
-
self, output_path:
|
320
|
+
self, output_path: str | Path, include_charts: bool = True
|
321
321
|
) -> Path:
|
322
322
|
"""Generate comprehensive live dashboard report.
|
323
323
|
|
@@ -396,7 +396,7 @@ class RealTimeDashboard:
|
|
396
396
|
|
397
397
|
return html_template
|
398
398
|
|
399
|
-
def _generate_status_section(self, metrics:
|
399
|
+
def _generate_status_section(self, metrics: LiveMetrics | None) -> str:
|
400
400
|
"""Generate status overview section."""
|
401
401
|
if not metrics:
|
402
402
|
return """
|
@@ -442,7 +442,7 @@ class RealTimeDashboard:
|
|
442
442
|
</section>
|
443
443
|
"""
|
444
444
|
|
445
|
-
def _generate_live_metrics_section(self, history:
|
445
|
+
def _generate_live_metrics_section(self, history: list[LiveMetrics]) -> str:
|
446
446
|
"""Generate live metrics charts section."""
|
447
447
|
if not history:
|
448
448
|
return """
|
@@ -860,7 +860,7 @@ class DashboardExporter:
|
|
860
860
|
self.dashboard = dashboard
|
861
861
|
self.logger = logger
|
862
862
|
|
863
|
-
def export_metrics_json(self, output_path:
|
863
|
+
def export_metrics_json(self, output_path: str | Path) -> Path:
|
864
864
|
"""Export current metrics as JSON.
|
865
865
|
|
866
866
|
Args:
|
@@ -895,7 +895,7 @@ class DashboardExporter:
|
|
895
895
|
self.logger.info(f"Exported metrics to: {output_path}")
|
896
896
|
return output_path
|
897
897
|
|
898
|
-
def _metrics_to_dict(self, metrics: LiveMetrics) ->
|
898
|
+
def _metrics_to_dict(self, metrics: LiveMetrics) -> dict[str, Any]:
|
899
899
|
"""Convert LiveMetrics to dictionary."""
|
900
900
|
return {
|
901
901
|
"timestamp": metrics.timestamp.isoformat(),
|
@@ -909,8 +909,8 @@ class DashboardExporter:
|
|
909
909
|
}
|
910
910
|
|
911
911
|
def create_dashboard_snapshot(
|
912
|
-
self, output_dir:
|
913
|
-
) ->
|
912
|
+
self, output_dir: str | Path, include_static_charts: bool = True
|
913
|
+
) -> dict[str, Path]:
|
914
914
|
"""Create complete dashboard snapshot with all assets.
|
915
915
|
|
916
916
|
Args:
|
@@ -23,7 +23,7 @@ Downstream Consumers:
|
|
23
23
|
|
24
24
|
import logging
|
25
25
|
from pathlib import Path
|
26
|
-
from typing import Any
|
26
|
+
from typing import Any
|
27
27
|
|
28
28
|
import matplotlib.pyplot as plt
|
29
29
|
import numpy as np
|
@@ -51,8 +51,8 @@ class PerformanceVisualizer:
|
|
51
51
|
self.logger = logger
|
52
52
|
|
53
53
|
def create_run_performance_summary(
|
54
|
-
self, run_id: str, output_dir:
|
55
|
-
) ->
|
54
|
+
self, run_id: str, output_dir: Path | None = None
|
55
|
+
) -> dict[str, Path]:
|
56
56
|
"""Create comprehensive performance summary for a workflow run.
|
57
57
|
|
58
58
|
Args:
|
@@ -63,8 +63,12 @@ class PerformanceVisualizer:
|
|
63
63
|
Dictionary mapping chart names to file paths
|
64
64
|
"""
|
65
65
|
if output_dir is None:
|
66
|
-
# Use
|
67
|
-
|
66
|
+
# Use centralized output directory
|
67
|
+
# Get project root and use data/outputs/visualizations/performance
|
68
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
69
|
+
output_dir = (
|
70
|
+
project_root / "data" / "outputs" / "visualizations" / "performance"
|
71
|
+
)
|
68
72
|
output_dir.mkdir(parents=True, exist_ok=True)
|
69
73
|
|
70
74
|
# Get run data
|
@@ -108,7 +112,7 @@ class PerformanceVisualizer:
|
|
108
112
|
return outputs
|
109
113
|
|
110
114
|
def _create_execution_timeline(
|
111
|
-
self, tasks:
|
115
|
+
self, tasks: list[TaskRun], output_path: Path
|
112
116
|
) -> Path:
|
113
117
|
"""Create Gantt-style execution timeline."""
|
114
118
|
fig, ax = plt.subplots(figsize=(12, max(6, len(tasks) * 0.5)))
|
@@ -206,7 +210,7 @@ class PerformanceVisualizer:
|
|
206
210
|
return output_path
|
207
211
|
|
208
212
|
def _create_resource_usage_chart(
|
209
|
-
self, tasks:
|
213
|
+
self, tasks: list[TaskRun], output_path: Path
|
210
214
|
) -> Path:
|
211
215
|
"""Create resource usage comparison chart."""
|
212
216
|
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 12))
|
@@ -254,7 +258,7 @@ class PerformanceVisualizer:
|
|
254
258
|
ax1.grid(True, axis="y", alpha=0.3)
|
255
259
|
|
256
260
|
# Add value labels
|
257
|
-
for bar, value in zip(bars1, cpu_usage):
|
261
|
+
for bar, value in zip(bars1, cpu_usage, strict=False):
|
258
262
|
if value > 0:
|
259
263
|
ax1.text(
|
260
264
|
bar.get_x() + bar.get_width() / 2,
|
@@ -291,7 +295,7 @@ class PerformanceVisualizer:
|
|
291
295
|
ax3.grid(True, axis="y", alpha=0.3)
|
292
296
|
|
293
297
|
# Add value labels
|
294
|
-
for bar, value in zip(bars3, durations):
|
298
|
+
for bar, value in zip(bars3, durations, strict=False):
|
295
299
|
if value > 0:
|
296
300
|
ax3.text(
|
297
301
|
bar.get_x() + bar.get_width() / 2,
|
@@ -310,7 +314,7 @@ class PerformanceVisualizer:
|
|
310
314
|
return output_path
|
311
315
|
|
312
316
|
def _create_node_performance_comparison(
|
313
|
-
self, tasks:
|
317
|
+
self, tasks: list[TaskRun], output_path: Path
|
314
318
|
) -> Path:
|
315
319
|
"""Create performance comparison radar chart."""
|
316
320
|
# Group tasks by node type
|
@@ -381,7 +385,9 @@ class PerformanceVisualizer:
|
|
381
385
|
|
382
386
|
colors = plt.cm.tab10(np.linspace(0, 1, len(avg_metrics)))
|
383
387
|
|
384
|
-
for (node_type, metrics), color in zip(
|
388
|
+
for (node_type, metrics), color in zip(
|
389
|
+
avg_metrics.items(), colors, strict=False
|
390
|
+
):
|
385
391
|
values = list(metrics.values())
|
386
392
|
|
387
393
|
# Normalize values to 0-100 scale for better visualization
|
@@ -400,7 +406,7 @@ class PerformanceVisualizer:
|
|
400
406
|
}
|
401
407
|
|
402
408
|
normalized_values = []
|
403
|
-
for cat, val in zip(categories, values):
|
409
|
+
for cat, val in zip(categories, values, strict=False):
|
404
410
|
normalized_values.append((val / max_vals[cat]) * 100)
|
405
411
|
|
406
412
|
normalized_values += normalized_values[:1]
|
@@ -431,7 +437,7 @@ class PerformanceVisualizer:
|
|
431
437
|
|
432
438
|
return output_path
|
433
439
|
|
434
|
-
def _create_io_analysis(self, tasks:
|
440
|
+
def _create_io_analysis(self, tasks: list[TaskRun], output_path: Path) -> Path:
|
435
441
|
"""Create I/O operations analysis chart."""
|
436
442
|
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
|
437
443
|
|
@@ -539,7 +545,7 @@ class PerformanceVisualizer:
|
|
539
545
|
return output_path
|
540
546
|
|
541
547
|
def _create_performance_heatmap(
|
542
|
-
self, tasks:
|
548
|
+
self, tasks: list[TaskRun], output_path: Path
|
543
549
|
) -> Path:
|
544
550
|
"""Create performance metrics heatmap."""
|
545
551
|
# Prepare data matrix
|
@@ -632,7 +638,7 @@ class PerformanceVisualizer:
|
|
632
638
|
return output_path
|
633
639
|
|
634
640
|
def _create_performance_report(
|
635
|
-
self, run: Any, tasks:
|
641
|
+
self, run: Any, tasks: list[TaskRun], output_path: Path
|
636
642
|
) -> Path:
|
637
643
|
"""Create markdown performance report."""
|
638
644
|
lines = []
|
@@ -714,12 +720,19 @@ class PerformanceVisualizer:
|
|
714
720
|
|
715
721
|
return output_path
|
716
722
|
|
717
|
-
def compare_runs(
|
718
|
-
self, run_ids: List[str], output_path: Optional[Path] = None
|
719
|
-
) -> Path:
|
723
|
+
def compare_runs(self, run_ids: list[str], output_path: Path | None = None) -> Path:
|
720
724
|
"""Compare performance across multiple runs."""
|
721
725
|
if output_path is None:
|
722
|
-
|
726
|
+
# Use centralized output directory
|
727
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
728
|
+
output_path = (
|
729
|
+
project_root
|
730
|
+
/ "data"
|
731
|
+
/ "outputs"
|
732
|
+
/ "visualizations"
|
733
|
+
/ "performance"
|
734
|
+
/ "comparison.png"
|
735
|
+
)
|
723
736
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
724
737
|
|
725
738
|
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
|
kailash/visualization/reports.py
CHANGED
@@ -27,7 +27,7 @@ from dataclasses import dataclass, field
|
|
27
27
|
from datetime import datetime
|
28
28
|
from enum import Enum
|
29
29
|
from pathlib import Path
|
30
|
-
from typing import Any
|
30
|
+
from typing import Any
|
31
31
|
|
32
32
|
import numpy as np
|
33
33
|
|
@@ -86,7 +86,7 @@ class PerformanceInsight:
|
|
86
86
|
title: str
|
87
87
|
description: str
|
88
88
|
recommendation: str
|
89
|
-
metrics:
|
89
|
+
metrics: dict[str, Any] = field(default_factory=dict)
|
90
90
|
|
91
91
|
|
92
92
|
@dataclass
|
@@ -134,9 +134,7 @@ class WorkflowPerformanceReporter:
|
|
134
134
|
report = reporter.generate_report(run_id, output_path="report.html")
|
135
135
|
"""
|
136
136
|
|
137
|
-
def __init__(
|
138
|
-
self, task_manager: TaskManager, config: Optional[ReportConfig] = None
|
139
|
-
):
|
137
|
+
def __init__(self, task_manager: TaskManager, config: ReportConfig | None = None):
|
140
138
|
"""Initialize performance reporter.
|
141
139
|
|
142
140
|
Args:
|
@@ -151,9 +149,9 @@ class WorkflowPerformanceReporter:
|
|
151
149
|
def generate_report(
|
152
150
|
self,
|
153
151
|
run_id: str,
|
154
|
-
output_path:
|
152
|
+
output_path: str | Path | None = None,
|
155
153
|
format: ReportFormat = ReportFormat.HTML,
|
156
|
-
compare_runs:
|
154
|
+
compare_runs: list[str] | None = None,
|
157
155
|
) -> Path:
|
158
156
|
"""Generate comprehensive performance report.
|
159
157
|
|
@@ -168,9 +166,13 @@ class WorkflowPerformanceReporter:
|
|
168
166
|
"""
|
169
167
|
if output_path is None:
|
170
168
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
169
|
+
# Use centralized output directory
|
170
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
171
171
|
output_path = (
|
172
|
-
|
172
|
+
project_root
|
173
|
+
/ "data"
|
173
174
|
/ "outputs"
|
175
|
+
/ "reports"
|
174
176
|
/ f"workflow_report_{run_id[:8]}_{timestamp}.{format.value}"
|
175
177
|
)
|
176
178
|
|
@@ -207,7 +209,7 @@ class WorkflowPerformanceReporter:
|
|
207
209
|
self.logger.info(f"Generated {format.value.upper()} report: {output_path}")
|
208
210
|
return output_path
|
209
211
|
|
210
|
-
def _analyze_workflow_run(self, run_id: str) ->
|
212
|
+
def _analyze_workflow_run(self, run_id: str) -> dict[str, Any]:
|
211
213
|
"""Perform detailed analysis of a workflow run.
|
212
214
|
|
213
215
|
Args:
|
@@ -260,7 +262,7 @@ class WorkflowPerformanceReporter:
|
|
260
262
|
}
|
261
263
|
|
262
264
|
def _calculate_workflow_summary(
|
263
|
-
self, run: Any, tasks:
|
265
|
+
self, run: Any, tasks: list[TaskRun]
|
264
266
|
) -> WorkflowSummary:
|
265
267
|
"""Calculate summary statistics for the workflow run."""
|
266
268
|
summary = WorkflowSummary(
|
@@ -336,7 +338,7 @@ class WorkflowPerformanceReporter:
|
|
336
338
|
|
337
339
|
return summary
|
338
340
|
|
339
|
-
def _analyze_task_performance(self, tasks:
|
341
|
+
def _analyze_task_performance(self, tasks: list[TaskRun]) -> dict[str, Any]:
|
340
342
|
"""Analyze performance patterns across tasks."""
|
341
343
|
analysis = {
|
342
344
|
"by_node_type": {},
|
@@ -399,7 +401,7 @@ class WorkflowPerformanceReporter:
|
|
399
401
|
|
400
402
|
return analysis
|
401
403
|
|
402
|
-
def _identify_bottlenecks(self, tasks:
|
404
|
+
def _identify_bottlenecks(self, tasks: list[TaskRun]) -> list[dict[str, Any]]:
|
403
405
|
"""Identify performance bottlenecks in the workflow."""
|
404
406
|
bottlenecks = []
|
405
407
|
|
@@ -493,7 +495,7 @@ class WorkflowPerformanceReporter:
|
|
493
495
|
|
494
496
|
return sorted(bottlenecks, key=lambda x: x["value"], reverse=True)
|
495
497
|
|
496
|
-
def _analyze_resource_utilization(self, tasks:
|
498
|
+
def _analyze_resource_utilization(self, tasks: list[TaskRun]) -> dict[str, Any]:
|
497
499
|
"""Analyze overall resource utilization patterns."""
|
498
500
|
analysis = {
|
499
501
|
"cpu_distribution": {},
|
@@ -577,7 +579,7 @@ class WorkflowPerformanceReporter:
|
|
577
579
|
|
578
580
|
return analysis
|
579
581
|
|
580
|
-
def _analyze_errors(self, tasks:
|
582
|
+
def _analyze_errors(self, tasks: list[TaskRun]) -> dict[str, Any]:
|
581
583
|
"""Analyze error patterns and failure modes."""
|
582
584
|
analysis = {
|
583
585
|
"error_summary": {},
|
@@ -630,7 +632,7 @@ class WorkflowPerformanceReporter:
|
|
630
632
|
|
631
633
|
return analysis
|
632
634
|
|
633
|
-
def _generate_insights(self, analysis:
|
635
|
+
def _generate_insights(self, analysis: dict[str, Any]) -> list[PerformanceInsight]:
|
634
636
|
"""Generate actionable insights from analysis results."""
|
635
637
|
insights = []
|
636
638
|
|
@@ -737,8 +739,8 @@ class WorkflowPerformanceReporter:
|
|
737
739
|
return insights
|
738
740
|
|
739
741
|
def _generate_analysis_charts(
|
740
|
-
self, run_id: str, tasks:
|
741
|
-
) ->
|
742
|
+
self, run_id: str, tasks: list[TaskRun]
|
743
|
+
) -> dict[str, str]:
|
742
744
|
"""Generate analysis charts and return file paths."""
|
743
745
|
charts = {}
|
744
746
|
|
@@ -751,7 +753,7 @@ class WorkflowPerformanceReporter:
|
|
751
753
|
|
752
754
|
return charts
|
753
755
|
|
754
|
-
def _compare_runs(self, run_ids:
|
756
|
+
def _compare_runs(self, run_ids: list[str]) -> dict[str, Any]:
|
755
757
|
"""Compare performance across multiple runs."""
|
756
758
|
comparison = {"runs": [], "trends": {}, "relative_performance": {}}
|
757
759
|
|
@@ -810,9 +812,9 @@ class WorkflowPerformanceReporter:
|
|
810
812
|
|
811
813
|
def _generate_html_report(
|
812
814
|
self,
|
813
|
-
analysis:
|
814
|
-
insights:
|
815
|
-
comparison_data:
|
815
|
+
analysis: dict[str, Any],
|
816
|
+
insights: list[PerformanceInsight],
|
817
|
+
comparison_data: dict[str, Any] | None = None,
|
816
818
|
) -> str:
|
817
819
|
"""Generate HTML report content."""
|
818
820
|
run_info = analysis["run_info"]
|
@@ -978,9 +980,9 @@ class WorkflowPerformanceReporter:
|
|
978
980
|
|
979
981
|
def _generate_markdown_report(
|
980
982
|
self,
|
981
|
-
analysis:
|
982
|
-
insights:
|
983
|
-
comparison_data:
|
983
|
+
analysis: dict[str, Any],
|
984
|
+
insights: list[PerformanceInsight],
|
985
|
+
comparison_data: dict[str, Any] | None = None,
|
984
986
|
) -> str:
|
985
987
|
"""Generate Markdown report content."""
|
986
988
|
run_info = analysis["run_info"]
|
@@ -1094,9 +1096,9 @@ class WorkflowPerformanceReporter:
|
|
1094
1096
|
|
1095
1097
|
def _generate_json_report(
|
1096
1098
|
self,
|
1097
|
-
analysis:
|
1098
|
-
insights:
|
1099
|
-
comparison_data:
|
1099
|
+
analysis: dict[str, Any],
|
1100
|
+
insights: list[PerformanceInsight],
|
1101
|
+
comparison_data: dict[str, Any] | None = None,
|
1100
1102
|
) -> str:
|
1101
1103
|
"""Generate JSON report content."""
|
1102
1104
|
report_data = {
|
@@ -1140,7 +1142,7 @@ class WorkflowPerformanceReporter:
|
|
1140
1142
|
|
1141
1143
|
return json.dumps(report_data, indent=2, default=str)
|
1142
1144
|
|
1143
|
-
def _generate_comparison_html(self, comparison_data:
|
1145
|
+
def _generate_comparison_html(self, comparison_data: dict[str, Any]) -> str:
|
1144
1146
|
"""Generate HTML for run comparison section."""
|
1145
1147
|
runs = comparison_data.get("runs", [])
|
1146
1148
|
trends = comparison_data.get("trends", {})
|