kailash 0.2.0__py3-none-any.whl → 0.2.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.
- kailash/api/custom_nodes_secure.py +2 -2
- kailash/api/studio_secure.py +1 -1
- kailash/mcp/client_new.py +1 -1
- kailash/mcp/server_new.py +6 -6
- kailash/nodes/ai/a2a.py +1 -1
- kailash/nodes/api/__init__.py +21 -0
- kailash/nodes/code/python.py +6 -0
- kailash/nodes/data/__init__.py +4 -2
- kailash/nodes/data/directory.py +278 -0
- kailash/nodes/data/sql.py +699 -256
- kailash/nodes/transform/processors.py +31 -0
- kailash/runtime/local.py +13 -0
- kailash/workflow/convergence.py +1 -1
- kailash/workflow/cycle_analyzer.py +346 -225
- kailash/workflow/cycle_builder.py +75 -69
- kailash/workflow/cycle_config.py +62 -46
- kailash/workflow/cycle_debugger.py +284 -184
- kailash/workflow/cycle_exceptions.py +111 -97
- kailash/workflow/cycle_profiler.py +272 -202
- kailash/workflow/graph.py +15 -0
- kailash/workflow/migration.py +238 -197
- kailash/workflow/templates.py +124 -105
- kailash/workflow/validation.py +356 -298
- kailash-0.2.2.dist-info/METADATA +121 -0
- {kailash-0.2.0.dist-info → kailash-0.2.2.dist-info}/RECORD +29 -28
- kailash-0.2.0.dist-info/METADATA +0 -1614
- {kailash-0.2.0.dist-info → kailash-0.2.2.dist-info}/WHEEL +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.2.dist-info}/entry_points.txt +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.2.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ analysis interface for understanding and improving cyclic workflow execution.
|
|
8
8
|
|
9
9
|
Examples:
|
10
10
|
Comprehensive cycle analysis:
|
11
|
-
|
11
|
+
|
12
12
|
>>> analyzer = CycleAnalyzer(
|
13
13
|
... analysis_level="comprehensive",
|
14
14
|
... output_directory="./analysis_results"
|
@@ -22,9 +22,9 @@ Examples:
|
|
22
22
|
>>> # Generate comprehensive report
|
23
23
|
>>> report = analyzer.generate_session_report()
|
24
24
|
>>> analyzer.export_analysis_data("analysis_results.json")
|
25
|
-
|
25
|
+
|
26
26
|
Real-time monitoring:
|
27
|
-
|
27
|
+
|
28
28
|
>>> # Monitor active cycle
|
29
29
|
>>> metrics = analyzer.get_real_time_metrics(trace)
|
30
30
|
>>> if metrics['health_score'] < 0.5:
|
@@ -47,12 +47,12 @@ logger = logging.getLogger(__name__)
|
|
47
47
|
class CycleAnalyzer:
|
48
48
|
"""
|
49
49
|
Comprehensive analysis tool combining debugging and profiling capabilities.
|
50
|
-
|
50
|
+
|
51
51
|
This class provides a unified interface for cycle analysis, combining
|
52
52
|
the detailed tracking capabilities of CycleDebugger with the performance
|
53
53
|
insights of CycleProfiler to provide comprehensive cycle optimization
|
54
54
|
guidance and health monitoring.
|
55
|
-
|
55
|
+
|
56
56
|
Examples:
|
57
57
|
>>> analyzer = CycleAnalyzer(analysis_level="comprehensive")
|
58
58
|
>>> # Start analysis
|
@@ -64,17 +64,17 @@ class CycleAnalyzer:
|
|
64
64
|
>>> analyzer.complete_cycle_analysis(trace, converged=True)
|
65
65
|
>>> report = analyzer.generate_comprehensive_report(session)
|
66
66
|
"""
|
67
|
-
|
67
|
+
|
68
68
|
def __init__(
|
69
69
|
self,
|
70
70
|
analysis_level: str = "standard",
|
71
71
|
enable_profiling: bool = True,
|
72
72
|
enable_debugging: bool = True,
|
73
|
-
output_directory: Optional[str] = None
|
73
|
+
output_directory: Optional[str] = None,
|
74
74
|
):
|
75
75
|
"""
|
76
76
|
Initialize cycle analyzer.
|
77
|
-
|
77
|
+
|
78
78
|
Args:
|
79
79
|
analysis_level: Level of analysis ("basic", "standard", "comprehensive").
|
80
80
|
enable_profiling: Whether to enable performance profiling.
|
@@ -85,197 +85,204 @@ class CycleAnalyzer:
|
|
85
85
|
self.enable_profiling = enable_profiling
|
86
86
|
self.enable_debugging = enable_debugging
|
87
87
|
self.output_directory = Path(output_directory) if output_directory else None
|
88
|
-
|
88
|
+
|
89
89
|
# Initialize components based on configuration
|
90
90
|
debug_level = {
|
91
91
|
"basic": "basic",
|
92
|
-
"standard": "detailed",
|
93
|
-
"comprehensive": "verbose"
|
92
|
+
"standard": "detailed",
|
93
|
+
"comprehensive": "verbose",
|
94
94
|
}.get(analysis_level, "detailed")
|
95
|
-
|
96
|
-
self.debugger =
|
97
|
-
debug_level=debug_level,
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
95
|
+
|
96
|
+
self.debugger = (
|
97
|
+
CycleDebugger(debug_level=debug_level, enable_profiling=enable_profiling)
|
98
|
+
if enable_debugging
|
99
|
+
else None
|
100
|
+
)
|
101
|
+
|
102
|
+
self.profiler = (
|
103
|
+
CycleProfiler(enable_advanced_metrics=(analysis_level == "comprehensive"))
|
104
|
+
if enable_profiling
|
105
|
+
else None
|
106
|
+
)
|
107
|
+
|
105
108
|
# Analysis session tracking
|
106
109
|
self.current_session: Optional[str] = None
|
107
110
|
self.session_traces: List[CycleExecutionTrace] = []
|
108
111
|
self.analysis_history: List[Dict[str, Any]] = []
|
109
|
-
|
112
|
+
|
110
113
|
# Create output directory if specified
|
111
114
|
if self.output_directory:
|
112
115
|
self.output_directory.mkdir(parents=True, exist_ok=True)
|
113
116
|
logger.info(f"Analysis output directory: {self.output_directory}")
|
114
|
-
|
117
|
+
|
115
118
|
def start_analysis_session(self, session_id: str) -> str:
|
116
119
|
"""
|
117
120
|
Start a new analysis session for grouping related cycles.
|
118
|
-
|
121
|
+
|
119
122
|
Analysis sessions allow grouping multiple cycle executions for
|
120
123
|
comparative analysis, trend identification, and comprehensive
|
121
124
|
reporting across related workflow executions.
|
122
|
-
|
125
|
+
|
123
126
|
Args:
|
124
127
|
session_id: Unique identifier for the analysis session.
|
125
|
-
|
128
|
+
|
126
129
|
Returns:
|
127
130
|
Session ID for reference.
|
128
|
-
|
131
|
+
|
129
132
|
Examples:
|
130
133
|
>>> session = analyzer.start_analysis_session("optimization_experiment_1")
|
131
134
|
"""
|
132
135
|
self.current_session = session_id
|
133
136
|
self.session_traces = []
|
134
|
-
|
137
|
+
|
135
138
|
logger.info(f"Started analysis session: {session_id}")
|
136
139
|
return session_id
|
137
|
-
|
140
|
+
|
138
141
|
def start_cycle_analysis(
|
139
142
|
self,
|
140
143
|
cycle_id: str,
|
141
144
|
workflow_id: str,
|
142
145
|
max_iterations: Optional[int] = None,
|
143
146
|
timeout: Optional[float] = None,
|
144
|
-
convergence_condition: Optional[str] = None
|
147
|
+
convergence_condition: Optional[str] = None,
|
145
148
|
) -> Optional[CycleExecutionTrace]:
|
146
149
|
"""
|
147
150
|
Start analysis for a new cycle execution.
|
148
|
-
|
151
|
+
|
149
152
|
Begins comprehensive tracking for a cycle execution, including
|
150
153
|
debugging and profiling as configured. Returns a trace object
|
151
154
|
for tracking iteration progress.
|
152
|
-
|
155
|
+
|
153
156
|
Args:
|
154
157
|
cycle_id: Unique identifier for the cycle.
|
155
158
|
workflow_id: Parent workflow identifier.
|
156
159
|
max_iterations: Configured iteration limit.
|
157
160
|
timeout: Configured timeout limit.
|
158
161
|
convergence_condition: Convergence condition.
|
159
|
-
|
162
|
+
|
160
163
|
Returns:
|
161
164
|
Trace object for tracking, or None if debugging disabled.
|
162
|
-
|
165
|
+
|
163
166
|
Examples:
|
164
167
|
>>> trace = analyzer.start_cycle_analysis("opt_cycle", "workflow_1", max_iterations=100)
|
165
168
|
"""
|
166
169
|
if not self.debugger:
|
167
170
|
logger.warning("Debugging not enabled - cannot create trace")
|
168
171
|
return None
|
169
|
-
|
172
|
+
|
170
173
|
trace = self.debugger.start_cycle(
|
171
174
|
cycle_id=cycle_id,
|
172
175
|
workflow_id=workflow_id,
|
173
176
|
max_iterations=max_iterations,
|
174
177
|
timeout=timeout,
|
175
|
-
convergence_condition=convergence_condition
|
178
|
+
convergence_condition=convergence_condition,
|
179
|
+
)
|
180
|
+
|
181
|
+
logger.info(
|
182
|
+
f"Started cycle analysis for '{cycle_id}' in session '{self.current_session}'"
|
176
183
|
)
|
177
|
-
|
178
|
-
logger.info(f"Started cycle analysis for '{cycle_id}' in session '{self.current_session}'")
|
179
184
|
return trace
|
180
|
-
|
185
|
+
|
181
186
|
def track_iteration(
|
182
187
|
self,
|
183
188
|
trace: CycleExecutionTrace,
|
184
189
|
input_data: Dict[str, Any],
|
185
190
|
output_data: Dict[str, Any],
|
186
191
|
convergence_value: Optional[float] = None,
|
187
|
-
node_executions: Optional[List[str]] = None
|
192
|
+
node_executions: Optional[List[str]] = None,
|
188
193
|
):
|
189
194
|
"""
|
190
195
|
Track a single cycle iteration with input/output data.
|
191
|
-
|
196
|
+
|
192
197
|
Records detailed information about a cycle iteration including
|
193
198
|
timing, resource usage, convergence metrics, and execution flow
|
194
199
|
for comprehensive analysis.
|
195
|
-
|
200
|
+
|
196
201
|
Args:
|
197
202
|
trace: Active trace object.
|
198
203
|
input_data: Input data for the iteration.
|
199
204
|
output_data: Output data from the iteration.
|
200
205
|
convergence_value: Convergence metric if available.
|
201
206
|
node_executions: List of executed nodes.
|
202
|
-
|
207
|
+
|
203
208
|
Examples:
|
204
209
|
>>> analyzer.track_iteration(trace, input_data, output_data, convergence_value=0.05)
|
205
210
|
"""
|
206
211
|
if not self.debugger:
|
207
212
|
return
|
208
|
-
|
213
|
+
|
209
214
|
iteration = self.debugger.start_iteration(trace, input_data)
|
210
215
|
self.debugger.end_iteration(
|
211
216
|
trace, iteration, output_data, convergence_value, node_executions
|
212
217
|
)
|
213
|
-
|
218
|
+
|
214
219
|
if self.analysis_level == "comprehensive":
|
215
220
|
logger.debug(
|
216
221
|
f"Tracked iteration {iteration.iteration_number} for cycle '{trace.cycle_id}' "
|
217
222
|
f"with convergence={convergence_value}"
|
218
223
|
)
|
219
|
-
|
224
|
+
|
220
225
|
def complete_cycle_analysis(
|
221
226
|
self,
|
222
227
|
trace: CycleExecutionTrace,
|
223
228
|
converged: bool,
|
224
229
|
termination_reason: str,
|
225
|
-
convergence_iteration: Optional[int] = None
|
230
|
+
convergence_iteration: Optional[int] = None,
|
226
231
|
):
|
227
232
|
"""
|
228
233
|
Complete cycle analysis and generate insights.
|
229
|
-
|
234
|
+
|
230
235
|
Finalizes cycle tracking and performs comprehensive analysis
|
231
236
|
including performance metrics, optimization recommendations,
|
232
237
|
and comparative insights if multiple cycles are available.
|
233
|
-
|
238
|
+
|
234
239
|
Args:
|
235
240
|
trace: Cycle trace to complete.
|
236
241
|
converged: Whether the cycle converged successfully.
|
237
242
|
termination_reason: Why the cycle terminated.
|
238
243
|
convergence_iteration: Iteration where convergence occurred.
|
239
|
-
|
244
|
+
|
240
245
|
Examples:
|
241
246
|
>>> analyzer.complete_cycle_analysis(trace, converged=True, termination_reason="convergence")
|
242
247
|
"""
|
243
248
|
if not self.debugger:
|
244
249
|
return
|
245
|
-
|
250
|
+
|
246
251
|
# Complete debugging
|
247
|
-
self.debugger.end_cycle(
|
248
|
-
|
252
|
+
self.debugger.end_cycle(
|
253
|
+
trace, converged, termination_reason, convergence_iteration
|
254
|
+
)
|
255
|
+
|
249
256
|
# Add to profiler for performance analysis
|
250
257
|
if self.profiler:
|
251
258
|
self.profiler.add_trace(trace)
|
252
|
-
|
259
|
+
|
253
260
|
# Add to session traces
|
254
261
|
self.session_traces.append(trace)
|
255
|
-
|
262
|
+
|
256
263
|
logger.info(
|
257
264
|
f"Completed cycle analysis for '{trace.cycle_id}' - "
|
258
265
|
f"converged={converged}, iterations={len(trace.iterations)}"
|
259
266
|
)
|
260
|
-
|
267
|
+
|
261
268
|
# Generate immediate insights for comprehensive analysis
|
262
269
|
if self.analysis_level == "comprehensive":
|
263
270
|
self._generate_immediate_insights(trace)
|
264
|
-
|
271
|
+
|
265
272
|
def generate_cycle_report(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
|
266
273
|
"""
|
267
274
|
Generate comprehensive report for a single cycle.
|
268
|
-
|
275
|
+
|
269
276
|
Creates a detailed analysis report for a specific cycle execution
|
270
277
|
including debugging information, performance metrics, and
|
271
278
|
optimization recommendations.
|
272
|
-
|
279
|
+
|
273
280
|
Args:
|
274
281
|
trace: Completed cycle trace.
|
275
|
-
|
282
|
+
|
276
283
|
Returns:
|
277
284
|
Comprehensive cycle analysis report.
|
278
|
-
|
285
|
+
|
279
286
|
Examples:
|
280
287
|
>>> report = analyzer.generate_cycle_report(trace)
|
281
288
|
>>> print(f"Cycle efficiency: {report['performance']['efficiency_score']}")
|
@@ -286,119 +293,137 @@ class CycleAnalyzer:
|
|
286
293
|
"workflow_id": trace.workflow_id,
|
287
294
|
"analysis_level": self.analysis_level,
|
288
295
|
"session_id": self.current_session,
|
289
|
-
"generated_at": datetime.now().isoformat()
|
296
|
+
"generated_at": datetime.now().isoformat(),
|
290
297
|
}
|
291
298
|
}
|
292
|
-
|
299
|
+
|
293
300
|
# Add debugging information
|
294
301
|
if self.debugger:
|
295
302
|
debug_report = self.debugger.generate_report(trace)
|
296
303
|
report["debugging"] = debug_report
|
297
|
-
|
304
|
+
|
298
305
|
# Add profiling information
|
299
306
|
if self.profiler:
|
300
307
|
# Create temporary profiler for single trace analysis
|
301
|
-
single_profiler = CycleProfiler(
|
308
|
+
single_profiler = CycleProfiler(
|
309
|
+
enable_advanced_metrics=(self.analysis_level == "comprehensive")
|
310
|
+
)
|
302
311
|
single_profiler.add_trace(trace)
|
303
|
-
|
312
|
+
|
304
313
|
performance_metrics = single_profiler.analyze_performance()
|
305
314
|
recommendations = single_profiler.get_optimization_recommendations(trace)
|
306
|
-
|
315
|
+
|
307
316
|
report["performance"] = performance_metrics.to_dict()
|
308
317
|
report["recommendations"] = recommendations
|
309
|
-
|
318
|
+
|
310
319
|
# Add analysis-level specific insights
|
311
320
|
if self.analysis_level == "comprehensive":
|
312
321
|
report["advanced_analysis"] = self._generate_advanced_analysis(trace)
|
313
|
-
|
322
|
+
|
314
323
|
# Export to file if configured
|
315
324
|
if self.output_directory:
|
316
325
|
self._export_cycle_report(report, trace.cycle_id)
|
317
|
-
|
326
|
+
|
318
327
|
return report
|
319
|
-
|
320
|
-
def generate_session_report(
|
328
|
+
|
329
|
+
def generate_session_report(
|
330
|
+
self, session_id: Optional[str] = None
|
331
|
+
) -> Dict[str, Any]:
|
321
332
|
"""
|
322
333
|
Generate comprehensive report for an analysis session.
|
323
|
-
|
334
|
+
|
324
335
|
Creates a detailed analysis report covering all cycles in a session,
|
325
336
|
including comparative analysis, trend identification, and overall
|
326
337
|
optimization recommendations.
|
327
|
-
|
338
|
+
|
328
339
|
Args:
|
329
340
|
session_id: Session to analyze, or current session if None.
|
330
|
-
|
341
|
+
|
331
342
|
Returns:
|
332
343
|
Comprehensive session analysis report.
|
333
|
-
|
344
|
+
|
334
345
|
Examples:
|
335
346
|
>>> report = analyzer.generate_session_report()
|
336
347
|
>>> print(f"Best cycle: {report['comparative_analysis']['best_cycle']}")
|
337
348
|
"""
|
338
349
|
target_session = session_id or self.current_session
|
339
350
|
traces_to_analyze = self.session_traces if session_id is None else []
|
340
|
-
|
351
|
+
|
341
352
|
report = {
|
342
353
|
"session_info": {
|
343
354
|
"session_id": target_session,
|
344
355
|
"analysis_level": self.analysis_level,
|
345
356
|
"cycles_analyzed": len(traces_to_analyze),
|
346
|
-
"generated_at": datetime.now().isoformat()
|
357
|
+
"generated_at": datetime.now().isoformat(),
|
347
358
|
},
|
348
359
|
"summary": {
|
349
360
|
"total_cycles": len(traces_to_analyze),
|
350
|
-
"total_iterations": sum(
|
351
|
-
|
352
|
-
|
353
|
-
|
361
|
+
"total_iterations": sum(
|
362
|
+
len(trace.iterations) for trace in traces_to_analyze
|
363
|
+
),
|
364
|
+
"convergence_rate": (
|
365
|
+
len([t for t in traces_to_analyze if t.converged])
|
366
|
+
/ len(traces_to_analyze)
|
367
|
+
if traces_to_analyze
|
368
|
+
else 0
|
369
|
+
),
|
370
|
+
"avg_cycle_time": (
|
371
|
+
sum(t.total_execution_time or 0 for t in traces_to_analyze)
|
372
|
+
/ len(traces_to_analyze)
|
373
|
+
if traces_to_analyze
|
374
|
+
else 0
|
375
|
+
),
|
376
|
+
},
|
354
377
|
}
|
355
|
-
|
378
|
+
|
356
379
|
if not traces_to_analyze:
|
357
380
|
report["warning"] = "No traces available for analysis"
|
358
381
|
return report
|
359
|
-
|
382
|
+
|
360
383
|
# Add profiling analysis
|
361
384
|
if self.profiler and traces_to_analyze:
|
362
385
|
# Ensure all traces are in profiler
|
363
386
|
for trace in traces_to_analyze:
|
364
387
|
if trace not in self.profiler.traces:
|
365
388
|
self.profiler.add_trace(trace)
|
366
|
-
|
389
|
+
|
367
390
|
performance_report = self.profiler.generate_performance_report()
|
368
391
|
report["performance_analysis"] = performance_report
|
369
|
-
|
392
|
+
|
370
393
|
# Add comparative analysis
|
371
394
|
if len(traces_to_analyze) >= 2:
|
372
395
|
cycle_ids = [trace.cycle_id for trace in traces_to_analyze]
|
373
|
-
comparison =
|
396
|
+
comparison = (
|
397
|
+
self.profiler.compare_cycles(cycle_ids) if self.profiler else {}
|
398
|
+
)
|
374
399
|
report["comparative_analysis"] = comparison
|
375
|
-
|
400
|
+
|
376
401
|
# Add session-specific insights
|
377
402
|
report["insights"] = self._generate_session_insights(traces_to_analyze)
|
378
|
-
|
403
|
+
|
379
404
|
# Export to file if configured
|
380
405
|
if self.output_directory:
|
381
406
|
self._export_session_report(report, target_session)
|
382
|
-
|
407
|
+
|
383
408
|
return report
|
384
|
-
|
409
|
+
|
385
410
|
def get_real_time_metrics(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
|
386
411
|
"""
|
387
412
|
Get real-time metrics for an active cycle.
|
388
|
-
|
413
|
+
|
389
414
|
Provides current performance metrics and health indicators
|
390
415
|
for a cycle that is currently executing, enabling real-time
|
391
416
|
monitoring and early intervention if issues are detected.
|
392
|
-
|
417
|
+
|
393
418
|
Args:
|
394
419
|
trace: Active cycle trace.
|
395
|
-
|
420
|
+
|
396
421
|
Returns:
|
397
422
|
Dict[str, Any]: Real-time metrics and health indicators
|
398
|
-
|
423
|
+
|
399
424
|
Side Effects:
|
400
425
|
None - this is a pure analysis method
|
401
|
-
|
426
|
+
|
402
427
|
Example:
|
403
428
|
>>> metrics = analyzer.get_real_time_metrics(trace)
|
404
429
|
>>> if metrics['health_score'] < 0.5:
|
@@ -406,23 +431,31 @@ class CycleAnalyzer:
|
|
406
431
|
"""
|
407
432
|
if not trace.iterations:
|
408
433
|
return {"status": "no_iterations", "health_score": 0.5}
|
409
|
-
|
434
|
+
|
410
435
|
recent_iterations = trace.iterations[-5:] # Last 5 iterations
|
411
|
-
|
436
|
+
|
412
437
|
# Calculate real-time performance indicators
|
413
|
-
avg_recent_time = sum(
|
414
|
-
|
438
|
+
avg_recent_time = sum(
|
439
|
+
iter.execution_time or 0 for iter in recent_iterations
|
440
|
+
) / len(recent_iterations)
|
441
|
+
|
415
442
|
# Memory trend (if available)
|
416
|
-
memory_values = [
|
443
|
+
memory_values = [
|
444
|
+
iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb
|
445
|
+
]
|
417
446
|
memory_trend = "stable"
|
418
447
|
if len(memory_values) >= 2:
|
419
448
|
if memory_values[-1] > memory_values[0] * 1.2:
|
420
449
|
memory_trend = "increasing"
|
421
450
|
elif memory_values[-1] < memory_values[0] * 0.8:
|
422
451
|
memory_trend = "decreasing"
|
423
|
-
|
452
|
+
|
424
453
|
# Convergence trend
|
425
|
-
convergence_values = [
|
454
|
+
convergence_values = [
|
455
|
+
iter.convergence_value
|
456
|
+
for iter in recent_iterations
|
457
|
+
if iter.convergence_value
|
458
|
+
]
|
426
459
|
convergence_trend = "unknown"
|
427
460
|
if len(convergence_values) >= 2:
|
428
461
|
if convergence_values[-1] < convergence_values[0]:
|
@@ -431,10 +464,10 @@ class CycleAnalyzer:
|
|
431
464
|
convergence_trend = "degrading"
|
432
465
|
else:
|
433
466
|
convergence_trend = "stable"
|
434
|
-
|
467
|
+
|
435
468
|
# Health score calculation
|
436
469
|
health_score = self._calculate_real_time_health_score(trace, recent_iterations)
|
437
|
-
|
470
|
+
|
438
471
|
return {
|
439
472
|
"status": "active",
|
440
473
|
"current_iteration": len(trace.iterations),
|
@@ -442,29 +475,29 @@ class CycleAnalyzer:
|
|
442
475
|
"memory_trend": memory_trend,
|
443
476
|
"convergence_trend": convergence_trend,
|
444
477
|
"health_score": health_score,
|
445
|
-
"alerts": self._generate_real_time_alerts(trace, recent_iterations)
|
478
|
+
"alerts": self._generate_real_time_alerts(trace, recent_iterations),
|
446
479
|
}
|
447
|
-
|
480
|
+
|
448
481
|
def export_analysis_data(
|
449
482
|
self,
|
450
483
|
filepath: Optional[str] = None,
|
451
484
|
format: str = "json",
|
452
|
-
include_traces: bool = True
|
485
|
+
include_traces: bool = True,
|
453
486
|
):
|
454
487
|
"""
|
455
488
|
Export comprehensive analysis data.
|
456
|
-
|
489
|
+
|
457
490
|
Exports all analysis data including traces, performance metrics,
|
458
491
|
and reports for external analysis, archival, or sharing.
|
459
|
-
|
492
|
+
|
460
493
|
Args:
|
461
494
|
filepath (Optional[str]): Output file path, auto-generated if None
|
462
495
|
format (str): Export format ("json", "csv")
|
463
496
|
include_traces (bool): Whether to include detailed trace data
|
464
|
-
|
497
|
+
|
465
498
|
Side Effects:
|
466
499
|
Creates export file with analysis data
|
467
|
-
|
500
|
+
|
468
501
|
Example:
|
469
502
|
>>> analyzer.export_analysis_data("cycle_analysis.json", include_traces=True)
|
470
503
|
"""
|
@@ -473,296 +506,384 @@ class CycleAnalyzer:
|
|
473
506
|
filepath = f"cycle_analysis_{self.current_session or 'session'}_{timestamp}.{format}"
|
474
507
|
if self.output_directory:
|
475
508
|
filepath = str(self.output_directory / filepath)
|
476
|
-
|
509
|
+
|
477
510
|
export_data = {
|
478
511
|
"analysis_metadata": {
|
479
512
|
"session_id": self.current_session,
|
480
513
|
"analysis_level": self.analysis_level,
|
481
514
|
"export_timestamp": datetime.now().isoformat(),
|
482
|
-
"cycles_count": len(self.session_traces)
|
515
|
+
"cycles_count": len(self.session_traces),
|
483
516
|
}
|
484
517
|
}
|
485
|
-
|
518
|
+
|
486
519
|
# Include session report
|
487
520
|
if self.session_traces:
|
488
521
|
export_data["session_report"] = self.generate_session_report()
|
489
|
-
|
522
|
+
|
490
523
|
# Include individual cycle reports
|
491
524
|
if include_traces:
|
492
525
|
export_data["cycle_reports"] = [
|
493
526
|
self.generate_cycle_report(trace) for trace in self.session_traces
|
494
527
|
]
|
495
|
-
|
528
|
+
|
496
529
|
# Include performance history if available
|
497
530
|
if self.profiler:
|
498
531
|
export_data["performance_history"] = [
|
499
532
|
metrics.to_dict() for metrics in self.profiler.performance_history
|
500
533
|
]
|
501
|
-
|
534
|
+
|
502
535
|
# Export to file
|
503
536
|
if format == "json":
|
504
|
-
with open(filepath,
|
537
|
+
with open(filepath, "w") as f:
|
505
538
|
json.dump(export_data, f, indent=2)
|
506
539
|
elif format == "csv":
|
507
540
|
# For CSV, export summary data only
|
508
541
|
import csv
|
509
|
-
|
542
|
+
|
543
|
+
with open(filepath, "w", newline="") as f:
|
510
544
|
writer = csv.writer(f)
|
511
|
-
|
545
|
+
|
512
546
|
# Write header
|
513
|
-
writer.writerow(
|
514
|
-
|
547
|
+
writer.writerow(
|
548
|
+
[
|
549
|
+
"cycle_id",
|
550
|
+
"workflow_id",
|
551
|
+
"iterations",
|
552
|
+
"execution_time",
|
553
|
+
"converged",
|
554
|
+
"efficiency_score",
|
555
|
+
]
|
556
|
+
)
|
557
|
+
|
515
558
|
# Write cycle data
|
516
559
|
for trace in self.session_traces:
|
517
560
|
stats = trace.get_statistics()
|
518
|
-
writer.writerow(
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
561
|
+
writer.writerow(
|
562
|
+
[
|
563
|
+
trace.cycle_id,
|
564
|
+
trace.workflow_id,
|
565
|
+
len(trace.iterations),
|
566
|
+
trace.total_execution_time,
|
567
|
+
trace.converged,
|
568
|
+
stats.get("efficiency_score", 0.0),
|
569
|
+
]
|
570
|
+
)
|
526
571
|
else:
|
527
572
|
raise ValueError(f"Unsupported export format: {format}")
|
528
|
-
|
573
|
+
|
529
574
|
logger.info(f"Exported analysis data to {filepath} in {format} format")
|
530
|
-
|
575
|
+
|
531
576
|
def _generate_immediate_insights(self, trace: CycleExecutionTrace):
|
532
577
|
"""Generate immediate insights for a completed cycle."""
|
533
578
|
stats = trace.get_statistics()
|
534
|
-
|
579
|
+
|
535
580
|
# Log key insights
|
536
581
|
if stats["efficiency_score"] > 0.8:
|
537
|
-
logger.info(
|
582
|
+
logger.info(
|
583
|
+
f"Excellent performance for cycle '{trace.cycle_id}' - efficiency: {stats['efficiency_score']:.2f}"
|
584
|
+
)
|
538
585
|
elif stats["efficiency_score"] < 0.3:
|
539
|
-
logger.warning(
|
540
|
-
|
586
|
+
logger.warning(
|
587
|
+
f"Poor performance for cycle '{trace.cycle_id}' - efficiency: {stats['efficiency_score']:.2f}"
|
588
|
+
)
|
589
|
+
|
541
590
|
if not trace.converged:
|
542
|
-
logger.warning(
|
543
|
-
|
591
|
+
logger.warning(
|
592
|
+
f"Cycle '{trace.cycle_id}' failed to converge - reason: {trace.termination_reason}"
|
593
|
+
)
|
594
|
+
|
544
595
|
# Check for performance issues
|
545
596
|
if stats["avg_iteration_time"] > 1.0:
|
546
|
-
logger.warning(
|
547
|
-
|
597
|
+
logger.warning(
|
598
|
+
f"Slow iterations detected for cycle '{trace.cycle_id}' - avg: {stats['avg_iteration_time']:.3f}s"
|
599
|
+
)
|
600
|
+
|
548
601
|
def _generate_advanced_analysis(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
|
549
602
|
"""Generate advanced analysis insights for comprehensive mode."""
|
550
603
|
convergence_trend = trace.get_convergence_trend()
|
551
|
-
|
604
|
+
|
552
605
|
# Convergence pattern analysis
|
553
606
|
convergence_analysis = {}
|
554
607
|
if convergence_trend:
|
555
608
|
values = [value for _, value in convergence_trend if value is not None]
|
556
609
|
if len(values) >= 3:
|
557
610
|
# Calculate convergence velocity
|
558
|
-
velocity = (
|
611
|
+
velocity = (
|
612
|
+
(values[0] - values[-1]) / len(values) if len(values) > 1 else 0
|
613
|
+
)
|
559
614
|
convergence_analysis = {
|
560
615
|
"convergence_velocity": velocity,
|
561
|
-
"convergence_pattern":
|
562
|
-
|
616
|
+
"convergence_pattern": (
|
617
|
+
"fast"
|
618
|
+
if velocity > 0.1
|
619
|
+
else "slow" if velocity > 0.01 else "minimal"
|
620
|
+
),
|
621
|
+
"stability_score": self._calculate_convergence_stability(values),
|
563
622
|
}
|
564
|
-
|
623
|
+
|
565
624
|
# Iteration pattern analysis
|
566
|
-
iteration_times = [
|
625
|
+
iteration_times = [
|
626
|
+
iter.execution_time for iter in trace.iterations if iter.execution_time
|
627
|
+
]
|
567
628
|
iteration_analysis = {}
|
568
629
|
if iteration_times:
|
569
630
|
import statistics
|
631
|
+
|
570
632
|
iteration_analysis = {
|
571
633
|
"time_distribution": {
|
572
634
|
"mean": statistics.mean(iteration_times),
|
573
635
|
"median": statistics.median(iteration_times),
|
574
|
-
"mode":
|
575
|
-
|
636
|
+
"mode": (
|
637
|
+
statistics.mode(iteration_times)
|
638
|
+
if len(set(iteration_times)) != len(iteration_times)
|
639
|
+
else None
|
640
|
+
),
|
641
|
+
"skewness": self._calculate_skewness(iteration_times),
|
576
642
|
},
|
577
|
-
"performance_trend": self._analyze_performance_trend(iteration_times)
|
643
|
+
"performance_trend": self._analyze_performance_trend(iteration_times),
|
578
644
|
}
|
579
|
-
|
645
|
+
|
580
646
|
return {
|
581
647
|
"convergence_analysis": convergence_analysis,
|
582
648
|
"iteration_analysis": iteration_analysis,
|
583
|
-
"resource_efficiency": self._analyze_resource_efficiency(trace)
|
649
|
+
"resource_efficiency": self._analyze_resource_efficiency(trace),
|
584
650
|
}
|
585
|
-
|
586
|
-
def _generate_session_insights(
|
651
|
+
|
652
|
+
def _generate_session_insights(
|
653
|
+
self, traces: List[CycleExecutionTrace]
|
654
|
+
) -> Dict[str, Any]:
|
587
655
|
"""Generate insights across multiple cycles in a session."""
|
588
656
|
if not traces:
|
589
657
|
return {}
|
590
|
-
|
658
|
+
|
591
659
|
# Find best and worst performing cycles
|
592
|
-
cycle_scores = {
|
660
|
+
cycle_scores = {
|
661
|
+
trace.cycle_id: trace.get_statistics()["efficiency_score"]
|
662
|
+
for trace in traces
|
663
|
+
}
|
593
664
|
best_cycle = max(cycle_scores.items(), key=lambda x: x[1])
|
594
665
|
worst_cycle = min(cycle_scores.items(), key=lambda x: x[1])
|
595
|
-
|
666
|
+
|
596
667
|
# Identify patterns
|
597
668
|
convergence_rate = len([t for t in traces if t.converged]) / len(traces)
|
598
669
|
avg_iterations = sum(len(t.iterations) for t in traces) / len(traces)
|
599
|
-
|
670
|
+
|
600
671
|
insights = {
|
601
672
|
"best_performing_cycle": {"id": best_cycle[0], "score": best_cycle[1]},
|
602
673
|
"worst_performing_cycle": {"id": worst_cycle[0], "score": worst_cycle[1]},
|
603
674
|
"overall_convergence_rate": convergence_rate,
|
604
675
|
"avg_iterations_per_cycle": avg_iterations,
|
605
|
-
"performance_consistency": best_cycle[1]
|
606
|
-
|
607
|
-
|
608
|
-
|
676
|
+
"performance_consistency": best_cycle[1]
|
677
|
+
- worst_cycle[1], # Lower is more consistent
|
678
|
+
"session_quality": (
|
679
|
+
"excellent"
|
680
|
+
if convergence_rate > 0.9 and cycle_scores[best_cycle[0]] > 0.8
|
681
|
+
else "good" if convergence_rate > 0.7 else "needs_improvement"
|
682
|
+
),
|
609
683
|
}
|
610
|
-
|
684
|
+
|
611
685
|
return insights
|
612
|
-
|
613
|
-
def _calculate_real_time_health_score(
|
686
|
+
|
687
|
+
def _calculate_real_time_health_score(
|
688
|
+
self, trace: CycleExecutionTrace, recent_iterations: List
|
689
|
+
) -> float:
|
614
690
|
"""Calculate real-time health score for an active cycle."""
|
615
691
|
score_components = []
|
616
|
-
|
692
|
+
|
617
693
|
# Performance component
|
618
694
|
if recent_iterations:
|
619
|
-
avg_time = sum(
|
620
|
-
|
695
|
+
avg_time = sum(
|
696
|
+
iter.execution_time or 0 for iter in recent_iterations
|
697
|
+
) / len(recent_iterations)
|
698
|
+
time_score = max(
|
699
|
+
0.0, 1.0 - min(1.0, avg_time / 2.0)
|
700
|
+
) # Penalty after 2s per iteration
|
621
701
|
score_components.append(time_score)
|
622
|
-
|
702
|
+
|
623
703
|
# Error rate component
|
624
704
|
error_count = len([iter for iter in recent_iterations if iter.error])
|
625
|
-
error_score =
|
705
|
+
error_score = (
|
706
|
+
max(0.0, 1.0 - (error_count / len(recent_iterations)))
|
707
|
+
if recent_iterations
|
708
|
+
else 1.0
|
709
|
+
)
|
626
710
|
score_components.append(error_score)
|
627
|
-
|
711
|
+
|
628
712
|
# Memory trend component (if available)
|
629
|
-
memory_values = [
|
713
|
+
memory_values = [
|
714
|
+
iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb
|
715
|
+
]
|
630
716
|
if memory_values and len(memory_values) >= 2:
|
631
717
|
memory_growth = (memory_values[-1] - memory_values[0]) / memory_values[0]
|
632
|
-
memory_score = max(
|
718
|
+
memory_score = max(
|
719
|
+
0.0, 1.0 - max(0.0, memory_growth)
|
720
|
+
) # Penalty for memory growth
|
633
721
|
score_components.append(memory_score)
|
634
|
-
|
635
|
-
return
|
636
|
-
|
637
|
-
|
722
|
+
|
723
|
+
return (
|
724
|
+
sum(score_components) / len(score_components) if score_components else 0.5
|
725
|
+
)
|
726
|
+
|
727
|
+
def _generate_real_time_alerts(
|
728
|
+
self, trace: CycleExecutionTrace, recent_iterations: List
|
729
|
+
) -> List[str]:
|
638
730
|
"""Generate real-time alerts for potential issues."""
|
639
731
|
alerts = []
|
640
|
-
|
732
|
+
|
641
733
|
# Check for slow iterations
|
642
734
|
if recent_iterations:
|
643
|
-
avg_time = sum(
|
735
|
+
avg_time = sum(
|
736
|
+
iter.execution_time or 0 for iter in recent_iterations
|
737
|
+
) / len(recent_iterations)
|
644
738
|
if avg_time > 2.0:
|
645
739
|
alerts.append(f"Slow iterations detected: {avg_time:.2f}s average")
|
646
|
-
|
740
|
+
|
647
741
|
# Check for errors
|
648
742
|
error_count = len([iter for iter in recent_iterations if iter.error])
|
649
743
|
if error_count > 0:
|
650
|
-
alerts.append(
|
651
|
-
|
744
|
+
alerts.append(
|
745
|
+
f"Errors detected in {error_count}/{len(recent_iterations)} recent iterations"
|
746
|
+
)
|
747
|
+
|
652
748
|
# Check for memory growth
|
653
|
-
memory_values = [
|
749
|
+
memory_values = [
|
750
|
+
iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb
|
751
|
+
]
|
654
752
|
if len(memory_values) >= 2:
|
655
753
|
memory_growth = (memory_values[-1] - memory_values[0]) / memory_values[0]
|
656
754
|
if memory_growth > 0.2:
|
657
|
-
alerts.append(
|
658
|
-
|
755
|
+
alerts.append(
|
756
|
+
f"Memory usage increasing: {memory_growth*100:.1f}% growth"
|
757
|
+
)
|
758
|
+
|
659
759
|
# Check for potential non-convergence
|
660
760
|
if len(trace.iterations) > (trace.max_iterations_configured or 100) * 0.8:
|
661
|
-
alerts.append(
|
662
|
-
|
761
|
+
alerts.append(
|
762
|
+
f"Approaching max iterations: {len(trace.iterations)}/{trace.max_iterations_configured}"
|
763
|
+
)
|
764
|
+
|
663
765
|
return alerts
|
664
|
-
|
766
|
+
|
665
767
|
def _calculate_convergence_stability(self, values: List[float]) -> float:
|
666
768
|
"""Calculate stability score for convergence values."""
|
667
769
|
if len(values) < 2:
|
668
770
|
return 1.0
|
669
|
-
|
771
|
+
|
670
772
|
import statistics
|
773
|
+
|
671
774
|
mean_val = statistics.mean(values)
|
672
775
|
if mean_val == 0:
|
673
776
|
return 1.0
|
674
|
-
|
777
|
+
|
675
778
|
stddev = statistics.stdev(values)
|
676
779
|
cv = stddev / mean_val # Coefficient of variation
|
677
|
-
|
780
|
+
|
678
781
|
# Lower CV means more stable
|
679
782
|
return max(0.0, 1.0 - min(1.0, cv))
|
680
|
-
|
783
|
+
|
681
784
|
def _calculate_skewness(self, data: List[float]) -> float:
|
682
785
|
"""Calculate skewness of data distribution."""
|
683
786
|
if len(data) < 3:
|
684
787
|
return 0.0
|
685
|
-
|
788
|
+
|
686
789
|
import statistics
|
790
|
+
|
687
791
|
mean_val = statistics.mean(data)
|
688
792
|
n = len(data)
|
689
793
|
variance = sum((x - mean_val) ** 2 for x in data) / n
|
690
794
|
if variance == 0:
|
691
795
|
return 0.0
|
692
|
-
|
693
|
-
std_dev = variance
|
694
|
-
skewness = sum((x - mean_val) ** 3 for x in data) / (n * std_dev
|
796
|
+
|
797
|
+
std_dev = variance**0.5
|
798
|
+
skewness = sum((x - mean_val) ** 3 for x in data) / (n * std_dev**3)
|
695
799
|
return skewness
|
696
|
-
|
800
|
+
|
697
801
|
def _analyze_performance_trend(self, iteration_times: List[float]) -> str:
|
698
802
|
"""Analyze performance trend over iterations."""
|
699
803
|
if len(iteration_times) < 3:
|
700
804
|
return "insufficient_data"
|
701
|
-
|
805
|
+
|
702
806
|
# Simple trend analysis
|
703
|
-
first_half = iteration_times[:len(iteration_times)//2]
|
704
|
-
second_half = iteration_times[len(iteration_times)//2:]
|
705
|
-
|
807
|
+
first_half = iteration_times[: len(iteration_times) // 2]
|
808
|
+
second_half = iteration_times[len(iteration_times) // 2 :]
|
809
|
+
|
706
810
|
import statistics
|
811
|
+
|
707
812
|
first_avg = statistics.mean(first_half)
|
708
813
|
second_avg = statistics.mean(second_half)
|
709
|
-
|
814
|
+
|
710
815
|
improvement = (first_avg - second_avg) / first_avg
|
711
|
-
|
816
|
+
|
712
817
|
if improvement > 0.1:
|
713
818
|
return "improving"
|
714
819
|
elif improvement < -0.1:
|
715
820
|
return "degrading"
|
716
821
|
else:
|
717
822
|
return "stable"
|
718
|
-
|
719
|
-
def _analyze_resource_efficiency(
|
823
|
+
|
824
|
+
def _analyze_resource_efficiency(
|
825
|
+
self, trace: CycleExecutionTrace
|
826
|
+
) -> Dict[str, Any]:
|
720
827
|
"""Analyze resource usage efficiency."""
|
721
|
-
memory_values = [
|
722
|
-
|
723
|
-
|
828
|
+
memory_values = [
|
829
|
+
iter.memory_usage_mb for iter in trace.iterations if iter.memory_usage_mb
|
830
|
+
]
|
831
|
+
cpu_values = [
|
832
|
+
iter.cpu_usage_percent
|
833
|
+
for iter in trace.iterations
|
834
|
+
if iter.cpu_usage_percent
|
835
|
+
]
|
836
|
+
|
724
837
|
efficiency = {}
|
725
|
-
|
838
|
+
|
726
839
|
if memory_values:
|
727
840
|
import statistics
|
841
|
+
|
728
842
|
efficiency["memory_efficiency"] = {
|
729
843
|
"peak_usage": max(memory_values),
|
730
844
|
"avg_usage": statistics.mean(memory_values),
|
731
|
-
"efficiency_score": max(
|
845
|
+
"efficiency_score": max(
|
846
|
+
0.0, 1.0 - (max(memory_values) / 2000)
|
847
|
+
), # Penalty after 2GB
|
732
848
|
}
|
733
|
-
|
849
|
+
|
734
850
|
if cpu_values:
|
735
851
|
import statistics
|
852
|
+
|
736
853
|
efficiency["cpu_efficiency"] = {
|
737
854
|
"peak_usage": max(cpu_values),
|
738
855
|
"avg_usage": statistics.mean(cpu_values),
|
739
|
-
"efficiency_score": min(
|
856
|
+
"efficiency_score": min(
|
857
|
+
1.0, statistics.mean(cpu_values) / 100
|
858
|
+
), # Higher CPU usage is better utilization
|
740
859
|
}
|
741
|
-
|
860
|
+
|
742
861
|
return efficiency
|
743
|
-
|
862
|
+
|
744
863
|
def _export_cycle_report(self, report: Dict[str, Any], cycle_id: str):
|
745
864
|
"""Export cycle report to file."""
|
746
865
|
if not self.output_directory:
|
747
866
|
return
|
748
|
-
|
749
|
-
filename =
|
867
|
+
|
868
|
+
filename = (
|
869
|
+
f"cycle_report_{cycle_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
870
|
+
)
|
750
871
|
filepath = self.output_directory / filename
|
751
|
-
|
752
|
-
with open(filepath,
|
872
|
+
|
873
|
+
with open(filepath, "w") as f:
|
753
874
|
json.dump(report, f, indent=2)
|
754
|
-
|
875
|
+
|
755
876
|
logger.debug(f"Exported cycle report to {filepath}")
|
756
|
-
|
877
|
+
|
757
878
|
def _export_session_report(self, report: Dict[str, Any], session_id: str):
|
758
879
|
"""Export session report to file."""
|
759
880
|
if not self.output_directory:
|
760
881
|
return
|
761
|
-
|
882
|
+
|
762
883
|
filename = f"session_report_{session_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
763
884
|
filepath = self.output_directory / filename
|
764
|
-
|
765
|
-
with open(filepath,
|
885
|
+
|
886
|
+
with open(filepath, "w") as f:
|
766
887
|
json.dump(report, f, indent=2)
|
767
|
-
|
768
|
-
logger.debug(f"Exported session report to {filepath}")
|
888
|
+
|
889
|
+
logger.debug(f"Exported session report to {filepath}")
|