kailash 0.2.0__py3-none-any.whl → 0.2.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/mcp/server_new.py +6 -6
- kailash/nodes/data/__init__.py +1 -2
- kailash/nodes/data/sql.py +699 -256
- 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/migration.py +238 -197
- kailash/workflow/templates.py +124 -105
- kailash/workflow/validation.py +356 -298
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/METADATA +4 -1
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/RECORD +18 -18
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/WHEEL +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/top_level.txt +0 -0
@@ -8,23 +8,23 @@ execution patterns and performance characteristics.
|
|
8
8
|
|
9
9
|
Examples:
|
10
10
|
Basic profiling setup:
|
11
|
-
|
11
|
+
|
12
12
|
>>> profiler = CycleProfiler(enable_advanced_metrics=True)
|
13
13
|
>>> # Add execution traces
|
14
14
|
>>> profiler.add_trace(execution_trace)
|
15
15
|
>>> # Analyze performance
|
16
16
|
>>> metrics = profiler.analyze_performance()
|
17
17
|
>>> print(f"Overall efficiency: {metrics.efficiency_score}")
|
18
|
-
|
18
|
+
|
19
19
|
Comparative analysis:
|
20
|
-
|
20
|
+
|
21
21
|
>>> # Compare multiple cycles
|
22
22
|
>>> comparison = profiler.compare_cycles(["cycle_1", "cycle_2", "cycle_3"])
|
23
23
|
>>> print(f"Best cycle: {comparison['best_cycle']}")
|
24
24
|
>>> print(f"Performance gaps: {comparison['significant_differences']}")
|
25
|
-
|
25
|
+
|
26
26
|
Optimization recommendations:
|
27
|
-
|
27
|
+
|
28
28
|
>>> # Get actionable recommendations
|
29
29
|
>>> recommendations = profiler.get_optimization_recommendations()
|
30
30
|
>>> for rec in recommendations:
|
@@ -32,9 +32,9 @@ Examples:
|
|
32
32
|
... print(f" Suggestion: {rec['suggestion']}")
|
33
33
|
... print(f" Current: {rec['current_value']}")
|
34
34
|
... print(f" Target: {rec['target_improvement']}")
|
35
|
-
|
35
|
+
|
36
36
|
Comprehensive reporting:
|
37
|
-
|
37
|
+
|
38
38
|
>>> # Generate detailed report
|
39
39
|
>>> report = profiler.generate_performance_report()
|
40
40
|
>>> # Export for external analysis
|
@@ -56,11 +56,11 @@ logger = logging.getLogger(__name__)
|
|
56
56
|
class PerformanceMetrics:
|
57
57
|
"""
|
58
58
|
Comprehensive performance metrics for cycle analysis.
|
59
|
-
|
59
|
+
|
60
60
|
This class aggregates and analyzes performance data from cycle executions,
|
61
61
|
providing detailed insights into timing, resource usage, and efficiency
|
62
62
|
characteristics of cyclic workflows.
|
63
|
-
|
63
|
+
|
64
64
|
Attributes:
|
65
65
|
total_cycles: Number of cycles analyzed.
|
66
66
|
total_iterations: Total iterations across all cycles.
|
@@ -75,12 +75,12 @@ class PerformanceMetrics:
|
|
75
75
|
bottlenecks: Identified performance bottlenecks.
|
76
76
|
optimization_opportunities: Suggested optimizations.
|
77
77
|
"""
|
78
|
-
|
78
|
+
|
79
79
|
total_cycles: int = 0
|
80
80
|
total_iterations: int = 0
|
81
81
|
avg_cycle_time: float = 0.0
|
82
82
|
avg_iteration_time: float = 0.0
|
83
|
-
min_iteration_time: float = float(
|
83
|
+
min_iteration_time: float = float("inf")
|
84
84
|
max_iteration_time: float = 0.0
|
85
85
|
iteration_time_stddev: float = 0.0
|
86
86
|
memory_stats: Dict[str, float] = field(default_factory=dict)
|
@@ -88,10 +88,10 @@ class PerformanceMetrics:
|
|
88
88
|
convergence_stats: Dict[str, Any] = field(default_factory=dict)
|
89
89
|
bottlenecks: List[str] = field(default_factory=list)
|
90
90
|
optimization_opportunities: List[str] = field(default_factory=list)
|
91
|
-
|
91
|
+
|
92
92
|
def to_dict(self) -> Dict[str, Any]:
|
93
93
|
"""Convert metrics to dictionary for serialization.
|
94
|
-
|
94
|
+
|
95
95
|
Returns:
|
96
96
|
Dictionary representation of performance metrics.
|
97
97
|
"""
|
@@ -101,69 +101,75 @@ class PerformanceMetrics:
|
|
101
101
|
"timing": {
|
102
102
|
"avg_cycle_time": self.avg_cycle_time,
|
103
103
|
"avg_iteration_time": self.avg_iteration_time,
|
104
|
-
"min_iteration_time":
|
104
|
+
"min_iteration_time": (
|
105
|
+
self.min_iteration_time
|
106
|
+
if self.min_iteration_time != float("inf")
|
107
|
+
else 0.0
|
108
|
+
),
|
105
109
|
"max_iteration_time": self.max_iteration_time,
|
106
|
-
"iteration_time_stddev": self.iteration_time_stddev
|
110
|
+
"iteration_time_stddev": self.iteration_time_stddev,
|
107
111
|
},
|
108
112
|
"memory_stats": self.memory_stats,
|
109
113
|
"cpu_stats": self.cpu_stats,
|
110
114
|
"convergence_stats": self.convergence_stats,
|
111
115
|
"bottlenecks": self.bottlenecks,
|
112
|
-
"optimization_opportunities": self.optimization_opportunities
|
116
|
+
"optimization_opportunities": self.optimization_opportunities,
|
113
117
|
}
|
114
118
|
|
115
119
|
|
116
120
|
class CycleProfiler:
|
117
121
|
"""
|
118
122
|
Advanced profiling and performance analysis for cyclic workflows.
|
119
|
-
|
123
|
+
|
120
124
|
This class provides comprehensive performance analysis capabilities for
|
121
125
|
cycles, including statistical analysis, bottleneck identification,
|
122
126
|
comparative analysis across multiple cycles, and detailed optimization
|
123
127
|
recommendations based on execution patterns.
|
124
|
-
|
128
|
+
|
125
129
|
Examples:
|
126
130
|
>>> profiler = CycleProfiler()
|
127
131
|
>>> profiler.add_trace(execution_trace)
|
128
132
|
>>> metrics = profiler.analyze_performance()
|
129
133
|
>>> recommendations = profiler.get_optimization_recommendations()
|
130
134
|
"""
|
131
|
-
|
135
|
+
|
132
136
|
def __init__(self, enable_advanced_metrics: bool = True):
|
133
137
|
"""
|
134
138
|
Initialize cycle profiler.
|
135
|
-
|
139
|
+
|
136
140
|
Args:
|
137
141
|
enable_advanced_metrics: Whether to enable advanced statistical analysis.
|
138
142
|
"""
|
139
143
|
self.enable_advanced_metrics = enable_advanced_metrics
|
140
144
|
self.traces: List[CycleExecutionTrace] = []
|
141
145
|
self.performance_history: List[PerformanceMetrics] = []
|
142
|
-
|
146
|
+
|
143
147
|
def add_trace(self, trace: CycleExecutionTrace):
|
144
148
|
"""
|
145
149
|
Add a cycle execution trace for analysis.
|
146
|
-
|
150
|
+
|
147
151
|
Args:
|
148
152
|
trace: Completed execution trace to analyze.
|
149
|
-
|
153
|
+
|
150
154
|
Examples:
|
151
155
|
>>> profiler.add_trace(execution_trace)
|
152
156
|
"""
|
153
157
|
self.traces.append(trace)
|
154
|
-
logger.debug(
|
155
|
-
|
158
|
+
logger.debug(
|
159
|
+
f"Added trace for cycle '{trace.cycle_id}' with {len(trace.iterations)} iterations"
|
160
|
+
)
|
161
|
+
|
156
162
|
def analyze_performance(self) -> PerformanceMetrics:
|
157
163
|
"""
|
158
164
|
Perform comprehensive performance analysis on all traces.
|
159
|
-
|
165
|
+
|
160
166
|
Analyzes all collected traces to generate comprehensive performance
|
161
167
|
metrics, identify bottlenecks, and provide optimization recommendations
|
162
168
|
based on statistical analysis of execution patterns.
|
163
|
-
|
169
|
+
|
164
170
|
Returns:
|
165
171
|
Comprehensive performance analysis results.
|
166
|
-
|
172
|
+
|
167
173
|
Examples:
|
168
174
|
>>> metrics = profiler.analyze_performance()
|
169
175
|
>>> print(f"Average cycle time: {metrics.avg_cycle_time:.3f}s")
|
@@ -171,18 +177,18 @@ class CycleProfiler:
|
|
171
177
|
if not self.traces:
|
172
178
|
logger.warning("No traces available for performance analysis")
|
173
179
|
return PerformanceMetrics()
|
174
|
-
|
180
|
+
|
175
181
|
# Collect all timing data
|
176
182
|
cycle_times = []
|
177
183
|
iteration_times = []
|
178
184
|
memory_values = []
|
179
185
|
cpu_values = []
|
180
186
|
convergence_data = []
|
181
|
-
|
187
|
+
|
182
188
|
for trace in self.traces:
|
183
189
|
if trace.total_execution_time:
|
184
190
|
cycle_times.append(trace.total_execution_time)
|
185
|
-
|
191
|
+
|
186
192
|
for iteration in trace.iterations:
|
187
193
|
if iteration.execution_time:
|
188
194
|
iteration_times.append(iteration.execution_time)
|
@@ -192,34 +198,36 @@ class CycleProfiler:
|
|
192
198
|
cpu_values.append(iteration.cpu_usage_percent)
|
193
199
|
if iteration.convergence_value:
|
194
200
|
convergence_data.append(iteration.convergence_value)
|
195
|
-
|
201
|
+
|
196
202
|
# Calculate basic metrics
|
197
203
|
metrics = PerformanceMetrics(
|
198
204
|
total_cycles=len(self.traces),
|
199
|
-
total_iterations=sum(len(trace.iterations) for trace in self.traces)
|
205
|
+
total_iterations=sum(len(trace.iterations) for trace in self.traces),
|
200
206
|
)
|
201
|
-
|
207
|
+
|
202
208
|
# Timing analysis
|
203
209
|
if cycle_times:
|
204
210
|
metrics.avg_cycle_time = statistics.mean(cycle_times)
|
205
|
-
|
211
|
+
|
206
212
|
if iteration_times:
|
207
213
|
metrics.avg_iteration_time = statistics.mean(iteration_times)
|
208
214
|
metrics.min_iteration_time = min(iteration_times)
|
209
215
|
metrics.max_iteration_time = max(iteration_times)
|
210
216
|
if len(iteration_times) > 1:
|
211
217
|
metrics.iteration_time_stddev = statistics.stdev(iteration_times)
|
212
|
-
|
218
|
+
|
213
219
|
# Memory analysis
|
214
220
|
if memory_values:
|
215
221
|
metrics.memory_stats = {
|
216
222
|
"avg": statistics.mean(memory_values),
|
217
223
|
"min": min(memory_values),
|
218
224
|
"max": max(memory_values),
|
219
|
-
"stddev":
|
220
|
-
|
225
|
+
"stddev": (
|
226
|
+
statistics.stdev(memory_values) if len(memory_values) > 1 else 0.0
|
227
|
+
),
|
228
|
+
"median": statistics.median(memory_values),
|
221
229
|
}
|
222
|
-
|
230
|
+
|
223
231
|
# CPU analysis
|
224
232
|
if cpu_values:
|
225
233
|
metrics.cpu_stats = {
|
@@ -227,290 +235,316 @@ class CycleProfiler:
|
|
227
235
|
"min": min(cpu_values),
|
228
236
|
"max": max(cpu_values),
|
229
237
|
"stddev": statistics.stdev(cpu_values) if len(cpu_values) > 1 else 0.0,
|
230
|
-
"median": statistics.median(cpu_values)
|
238
|
+
"median": statistics.median(cpu_values),
|
231
239
|
}
|
232
|
-
|
240
|
+
|
233
241
|
# Convergence analysis
|
234
242
|
if convergence_data:
|
235
|
-
metrics.convergence_stats = self._analyze_convergence_performance(
|
236
|
-
|
243
|
+
metrics.convergence_stats = self._analyze_convergence_performance(
|
244
|
+
convergence_data
|
245
|
+
)
|
246
|
+
|
237
247
|
# Advanced analysis if enabled
|
238
248
|
if self.enable_advanced_metrics:
|
239
249
|
metrics.bottlenecks = self._identify_bottlenecks(metrics)
|
240
250
|
metrics.optimization_opportunities = self._identify_optimizations(metrics)
|
241
|
-
|
251
|
+
|
242
252
|
# Store in history
|
243
253
|
self.performance_history.append(metrics)
|
244
|
-
|
254
|
+
|
245
255
|
logger.info(
|
246
256
|
f"Analyzed performance for {metrics.total_cycles} cycles, "
|
247
257
|
f"{metrics.total_iterations} iterations, "
|
248
258
|
f"avg cycle time: {metrics.avg_cycle_time:.3f}s"
|
249
259
|
)
|
250
|
-
|
260
|
+
|
251
261
|
return metrics
|
252
|
-
|
262
|
+
|
253
263
|
def compare_cycles(self, cycle_ids: List[str]) -> Dict[str, Any]:
|
254
264
|
"""
|
255
265
|
Compare performance across multiple specific cycles.
|
256
|
-
|
266
|
+
|
257
267
|
Provides detailed comparative analysis between specific cycles,
|
258
268
|
highlighting performance differences, convergence patterns, and
|
259
269
|
relative efficiency metrics.
|
260
|
-
|
270
|
+
|
261
271
|
Args:
|
262
272
|
cycle_ids (List[str]): List of cycle IDs to compare
|
263
|
-
|
273
|
+
|
264
274
|
Returns:
|
265
275
|
Dict[str, Any]: Comparative analysis results
|
266
|
-
|
276
|
+
|
267
277
|
Side Effects:
|
268
278
|
None - this is a pure analysis method
|
269
|
-
|
279
|
+
|
270
280
|
Example:
|
271
281
|
>>> comparison = profiler.compare_cycles(["cycle_1", "cycle_2"])
|
272
282
|
>>> print(f"Best performing cycle: {comparison['best_cycle']}")
|
273
283
|
"""
|
274
|
-
relevant_traces = [
|
275
|
-
|
284
|
+
relevant_traces = [
|
285
|
+
trace for trace in self.traces if trace.cycle_id in cycle_ids
|
286
|
+
]
|
287
|
+
|
276
288
|
if len(relevant_traces) < 2:
|
277
289
|
return {"error": "Need at least 2 cycles for comparison"}
|
278
|
-
|
290
|
+
|
279
291
|
comparison = {
|
280
292
|
"cycles_compared": len(relevant_traces),
|
281
293
|
"cycle_details": {},
|
282
294
|
"performance_ranking": [],
|
283
|
-
"significant_differences": []
|
295
|
+
"significant_differences": [],
|
284
296
|
}
|
285
|
-
|
297
|
+
|
286
298
|
# Analyze each cycle
|
287
299
|
for trace in relevant_traces:
|
288
300
|
stats = trace.get_statistics()
|
289
|
-
|
301
|
+
|
290
302
|
comparison["cycle_details"][trace.cycle_id] = {
|
291
303
|
"execution_time": trace.total_execution_time,
|
292
304
|
"iterations": len(trace.iterations),
|
293
305
|
"converged": trace.converged,
|
294
306
|
"efficiency_score": stats["efficiency_score"],
|
295
307
|
"avg_iteration_time": stats["avg_iteration_time"],
|
296
|
-
"convergence_rate": stats["convergence_rate"]
|
308
|
+
"convergence_rate": stats["convergence_rate"],
|
297
309
|
}
|
298
|
-
|
310
|
+
|
299
311
|
# Rank by efficiency score
|
300
312
|
ranking = sorted(
|
301
313
|
comparison["cycle_details"].items(),
|
302
314
|
key=lambda x: x[1]["efficiency_score"],
|
303
|
-
reverse=True
|
315
|
+
reverse=True,
|
304
316
|
)
|
305
317
|
comparison["performance_ranking"] = [cycle_id for cycle_id, _ in ranking]
|
306
318
|
comparison["best_cycle"] = ranking[0][0] if ranking else None
|
307
319
|
comparison["worst_cycle"] = ranking[-1][0] if ranking else None
|
308
|
-
|
320
|
+
|
309
321
|
# Identify significant differences
|
310
322
|
if len(ranking) >= 2:
|
311
323
|
best_score = ranking[0][1]["efficiency_score"]
|
312
324
|
worst_score = ranking[-1][1]["efficiency_score"]
|
313
|
-
|
325
|
+
|
314
326
|
if best_score - worst_score > 0.2:
|
315
327
|
comparison["significant_differences"].append(
|
316
328
|
f"Large efficiency gap: {best_score:.2f} vs {worst_score:.2f}"
|
317
329
|
)
|
318
|
-
|
330
|
+
|
319
331
|
# Compare convergence rates
|
320
332
|
convergence_rates = [details["convergence_rate"] for _, details in ranking]
|
321
333
|
if max(convergence_rates) - min(convergence_rates) > 0.3:
|
322
334
|
comparison["significant_differences"].append(
|
323
335
|
"Significant variation in convergence rates"
|
324
336
|
)
|
325
|
-
|
337
|
+
|
326
338
|
return comparison
|
327
|
-
|
328
|
-
def get_optimization_recommendations(
|
339
|
+
|
340
|
+
def get_optimization_recommendations(
|
341
|
+
self, trace: Optional[CycleExecutionTrace] = None
|
342
|
+
) -> List[Dict[str, Any]]:
|
329
343
|
"""
|
330
344
|
Generate detailed optimization recommendations.
|
331
|
-
|
345
|
+
|
332
346
|
Provides specific, actionable optimization recommendations based on
|
333
347
|
performance analysis, including parameter tuning suggestions,
|
334
348
|
algorithmic improvements, and resource optimization strategies.
|
335
|
-
|
349
|
+
|
336
350
|
Args:
|
337
351
|
trace (Optional[CycleExecutionTrace]): Specific trace to analyze, or None for overall recommendations
|
338
|
-
|
352
|
+
|
339
353
|
Returns:
|
340
354
|
List[Dict[str, Any]]: List of optimization recommendations with details
|
341
|
-
|
355
|
+
|
342
356
|
Side Effects:
|
343
357
|
None - this is a pure analysis method
|
344
|
-
|
358
|
+
|
345
359
|
Example:
|
346
360
|
>>> recommendations = profiler.get_optimization_recommendations()
|
347
361
|
>>> for rec in recommendations:
|
348
362
|
... print(f"{rec['priority']}: {rec['description']}")
|
349
363
|
"""
|
350
364
|
recommendations = []
|
351
|
-
|
365
|
+
|
352
366
|
if trace:
|
353
367
|
traces_to_analyze = [trace]
|
354
368
|
else:
|
355
369
|
traces_to_analyze = self.traces
|
356
|
-
|
370
|
+
|
357
371
|
if not traces_to_analyze:
|
358
372
|
return recommendations
|
359
|
-
|
373
|
+
|
360
374
|
# Analyze all traces for patterns
|
361
375
|
self.analyze_performance() if not trace else None
|
362
|
-
|
376
|
+
|
363
377
|
for target_trace in traces_to_analyze:
|
364
378
|
stats = target_trace.get_statistics()
|
365
|
-
|
379
|
+
|
366
380
|
# Efficiency recommendations
|
367
381
|
if stats["efficiency_score"] < 0.3:
|
368
|
-
recommendations.append(
|
369
|
-
|
370
|
-
"category": "efficiency",
|
371
|
-
"description": "Very low efficiency detected",
|
372
|
-
"suggestion": "Consider reducing max_iterations or improving convergence condition",
|
373
|
-
"cycle_id": target_trace.cycle_id,
|
374
|
-
"current_value": stats["efficiency_score"],
|
375
|
-
"target_improvement": "Increase to > 0.5"
|
376
|
-
})
|
377
|
-
|
378
|
-
# Convergence recommendations
|
379
|
-
if not target_trace.converged:
|
380
|
-
if stats["total_iterations"] >= (target_trace.max_iterations_configured or 0):
|
381
|
-
recommendations.append({
|
382
|
+
recommendations.append(
|
383
|
+
{
|
382
384
|
"priority": "HIGH",
|
383
|
-
"category": "
|
384
|
-
"description": "
|
385
|
-
"suggestion": "
|
385
|
+
"category": "efficiency",
|
386
|
+
"description": "Very low efficiency detected",
|
387
|
+
"suggestion": "Consider reducing max_iterations or improving convergence condition",
|
386
388
|
"cycle_id": target_trace.cycle_id,
|
387
|
-
"current_value":
|
388
|
-
"target_improvement":
|
389
|
-
}
|
390
|
-
|
389
|
+
"current_value": stats["efficiency_score"],
|
390
|
+
"target_improvement": "Increase to > 0.5",
|
391
|
+
}
|
392
|
+
)
|
393
|
+
|
394
|
+
# Convergence recommendations
|
395
|
+
if not target_trace.converged:
|
396
|
+
if stats["total_iterations"] >= (
|
397
|
+
target_trace.max_iterations_configured or 0
|
398
|
+
):
|
399
|
+
recommendations.append(
|
400
|
+
{
|
401
|
+
"priority": "HIGH",
|
402
|
+
"category": "convergence",
|
403
|
+
"description": "Cycle reached max_iterations without converging",
|
404
|
+
"suggestion": "Increase max_iterations or improve algorithm",
|
405
|
+
"cycle_id": target_trace.cycle_id,
|
406
|
+
"current_value": target_trace.max_iterations_configured,
|
407
|
+
"target_improvement": f"Increase to {int((target_trace.max_iterations_configured or 10) * 1.5)}",
|
408
|
+
}
|
409
|
+
)
|
410
|
+
|
391
411
|
# Performance recommendations
|
392
412
|
if stats["avg_iteration_time"] > 0.5:
|
393
|
-
recommendations.append(
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
413
|
+
recommendations.append(
|
414
|
+
{
|
415
|
+
"priority": "MEDIUM",
|
416
|
+
"category": "performance",
|
417
|
+
"description": "High average iteration time",
|
418
|
+
"suggestion": "Optimize node execution or reduce data processing",
|
419
|
+
"cycle_id": target_trace.cycle_id,
|
420
|
+
"current_value": stats["avg_iteration_time"],
|
421
|
+
"target_improvement": "Reduce to < 0.5s per iteration",
|
422
|
+
}
|
423
|
+
)
|
424
|
+
|
403
425
|
# Memory recommendations
|
404
426
|
if target_trace.memory_peak_mb and target_trace.memory_peak_mb > 1000:
|
405
|
-
recommendations.append(
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
427
|
+
recommendations.append(
|
428
|
+
{
|
429
|
+
"priority": "MEDIUM",
|
430
|
+
"category": "memory",
|
431
|
+
"description": "High memory usage detected",
|
432
|
+
"suggestion": "Consider data streaming, chunking, or garbage collection",
|
433
|
+
"cycle_id": target_trace.cycle_id,
|
434
|
+
"current_value": target_trace.memory_peak_mb,
|
435
|
+
"target_improvement": "Reduce to < 1000 MB",
|
436
|
+
}
|
437
|
+
)
|
438
|
+
|
415
439
|
# Convergence pattern recommendations
|
416
440
|
convergence_trend = target_trace.get_convergence_trend()
|
417
441
|
if convergence_trend:
|
418
442
|
pattern_analysis = self._analyze_convergence_pattern(convergence_trend)
|
419
443
|
if pattern_analysis["unstable"]:
|
420
|
-
recommendations.append(
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
444
|
+
recommendations.append(
|
445
|
+
{
|
446
|
+
"priority": "HIGH",
|
447
|
+
"category": "stability",
|
448
|
+
"description": "Unstable convergence pattern detected",
|
449
|
+
"suggestion": "Reduce learning rate or add regularization",
|
450
|
+
"cycle_id": target_trace.cycle_id,
|
451
|
+
"current_value": "Unstable",
|
452
|
+
"target_improvement": "Stable convergence",
|
453
|
+
}
|
454
|
+
)
|
455
|
+
|
430
456
|
# Sort by priority
|
431
457
|
priority_order = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
|
432
458
|
recommendations.sort(key=lambda x: priority_order.get(x["priority"], 3))
|
433
|
-
|
459
|
+
|
434
460
|
return recommendations
|
435
|
-
|
461
|
+
|
436
462
|
def generate_performance_report(self) -> Dict[str, Any]:
|
437
463
|
"""
|
438
464
|
Generate comprehensive performance report.
|
439
|
-
|
465
|
+
|
440
466
|
Creates a detailed performance report including metrics analysis,
|
441
467
|
recommendations, trends, and comparative insights across all
|
442
468
|
analyzed cycles.
|
443
|
-
|
469
|
+
|
444
470
|
Returns:
|
445
471
|
Dict[str, Any]: Comprehensive performance report
|
446
|
-
|
472
|
+
|
447
473
|
Side Effects:
|
448
474
|
None - this is a pure analysis method
|
449
|
-
|
475
|
+
|
450
476
|
Example:
|
451
477
|
>>> report = profiler.generate_performance_report()
|
452
478
|
>>> print(f"Overall score: {report['overall_score']}")
|
453
479
|
"""
|
454
480
|
metrics = self.analyze_performance()
|
455
|
-
|
481
|
+
|
456
482
|
# Calculate overall performance score
|
457
483
|
overall_score = self._calculate_overall_score(metrics)
|
458
|
-
|
484
|
+
|
459
485
|
# Generate trend analysis if we have history
|
460
|
-
trend_analysis =
|
461
|
-
|
486
|
+
trend_analysis = (
|
487
|
+
self._analyze_performance_trends()
|
488
|
+
if len(self.performance_history) > 1
|
489
|
+
else None
|
490
|
+
)
|
491
|
+
|
462
492
|
# Get top recommendations
|
463
493
|
recommendations = self.get_optimization_recommendations()
|
464
|
-
|
494
|
+
|
465
495
|
report = {
|
466
496
|
"summary": {
|
467
497
|
"overall_score": overall_score,
|
468
498
|
"total_cycles_analyzed": metrics.total_cycles,
|
469
499
|
"total_iterations": metrics.total_iterations,
|
470
500
|
"avg_cycle_time": metrics.avg_cycle_time,
|
471
|
-
"primary_bottlenecks": metrics.bottlenecks[:3]
|
501
|
+
"primary_bottlenecks": metrics.bottlenecks[:3],
|
472
502
|
},
|
473
503
|
"detailed_metrics": metrics.to_dict(),
|
474
504
|
"trend_analysis": trend_analysis,
|
475
505
|
"recommendations": recommendations[:10], # Top 10 recommendations
|
476
506
|
"cycle_comparisons": self._get_cycle_comparisons(),
|
477
|
-
"generated_at": datetime.now().isoformat()
|
507
|
+
"generated_at": datetime.now().isoformat(),
|
478
508
|
}
|
479
|
-
|
509
|
+
|
480
510
|
return report
|
481
|
-
|
511
|
+
|
482
512
|
def export_profile_data(self, filepath: str, format: str = "json"):
|
483
513
|
"""
|
484
514
|
Export profiling data for external analysis.
|
485
|
-
|
515
|
+
|
486
516
|
Args:
|
487
517
|
filepath (str): Output file path
|
488
518
|
format (str): Export format ("json", "csv")
|
489
|
-
|
519
|
+
|
490
520
|
Side Effects:
|
491
521
|
Creates file with profiling data
|
492
|
-
|
522
|
+
|
493
523
|
Example:
|
494
524
|
>>> profiler.export_profile_data("profile_analysis.json")
|
495
525
|
"""
|
496
526
|
report = self.generate_performance_report()
|
497
|
-
|
527
|
+
|
498
528
|
if format == "json":
|
499
529
|
import json
|
500
|
-
|
530
|
+
|
531
|
+
with open(filepath, "w") as f:
|
501
532
|
json.dump(report, f, indent=2)
|
502
533
|
elif format == "csv":
|
503
534
|
import csv
|
504
|
-
|
535
|
+
|
536
|
+
with open(filepath, "w", newline="") as f:
|
505
537
|
writer = csv.writer(f)
|
506
|
-
|
538
|
+
|
507
539
|
# Write summary data
|
508
540
|
writer.writerow(["Metric", "Value"])
|
509
541
|
writer.writerow(["Overall Score", report["summary"]["overall_score"]])
|
510
|
-
writer.writerow(
|
542
|
+
writer.writerow(
|
543
|
+
["Total Cycles", report["summary"]["total_cycles_analyzed"]]
|
544
|
+
)
|
511
545
|
writer.writerow(["Avg Cycle Time", report["summary"]["avg_cycle_time"]])
|
512
546
|
writer.writerow([])
|
513
|
-
|
547
|
+
|
514
548
|
# Write detailed metrics
|
515
549
|
writer.writerow(["Detailed Metrics"])
|
516
550
|
metrics = report["detailed_metrics"]
|
@@ -522,150 +556,186 @@ class CycleProfiler:
|
|
522
556
|
writer.writerow([key, value])
|
523
557
|
else:
|
524
558
|
raise ValueError(f"Unsupported export format: {format}")
|
525
|
-
|
559
|
+
|
526
560
|
logger.info(f"Exported profiling data to {filepath} in {format} format")
|
527
|
-
|
528
|
-
def _analyze_convergence_performance(
|
561
|
+
|
562
|
+
def _analyze_convergence_performance(
|
563
|
+
self, convergence_data: List[float]
|
564
|
+
) -> Dict[str, Any]:
|
529
565
|
"""Analyze convergence performance characteristics."""
|
530
566
|
if not convergence_data:
|
531
567
|
return {}
|
532
|
-
|
568
|
+
|
533
569
|
return {
|
534
570
|
"avg_convergence": statistics.mean(convergence_data),
|
535
571
|
"min_convergence": min(convergence_data),
|
536
572
|
"max_convergence": max(convergence_data),
|
537
|
-
"convergence_stddev":
|
538
|
-
|
539
|
-
|
573
|
+
"convergence_stddev": (
|
574
|
+
statistics.stdev(convergence_data) if len(convergence_data) > 1 else 0.0
|
575
|
+
),
|
576
|
+
"convergence_trend": (
|
577
|
+
"improving"
|
578
|
+
if convergence_data[0] > convergence_data[-1]
|
579
|
+
else "degrading"
|
580
|
+
),
|
581
|
+
"data_points": len(convergence_data),
|
540
582
|
}
|
541
|
-
|
542
|
-
def _analyze_convergence_pattern(
|
583
|
+
|
584
|
+
def _analyze_convergence_pattern(
|
585
|
+
self, convergence_trend: List[Tuple[int, Optional[float]]]
|
586
|
+
) -> Dict[str, Any]:
|
543
587
|
"""Analyze convergence pattern for stability."""
|
544
588
|
valid_points = [value for _, value in convergence_trend if value is not None]
|
545
|
-
|
589
|
+
|
546
590
|
if len(valid_points) < 3:
|
547
591
|
return {"unstable": False, "reason": "insufficient_data"}
|
548
|
-
|
592
|
+
|
549
593
|
# Calculate volatility
|
550
|
-
differences = [
|
594
|
+
differences = [
|
595
|
+
abs(valid_points[i] - valid_points[i - 1])
|
596
|
+
for i in range(1, len(valid_points))
|
597
|
+
]
|
551
598
|
avg_difference = statistics.mean(differences)
|
552
599
|
max_difference = max(differences)
|
553
|
-
|
600
|
+
|
554
601
|
# Consider unstable if large swings or high volatility
|
555
602
|
unstable = max_difference > (2 * avg_difference) and avg_difference > 0.1
|
556
|
-
|
603
|
+
|
557
604
|
return {
|
558
605
|
"unstable": unstable,
|
559
606
|
"avg_volatility": avg_difference,
|
560
607
|
"max_volatility": max_difference,
|
561
|
-
"reason": "high_volatility" if unstable else "stable"
|
608
|
+
"reason": "high_volatility" if unstable else "stable",
|
562
609
|
}
|
563
|
-
|
610
|
+
|
564
611
|
def _identify_bottlenecks(self, metrics: PerformanceMetrics) -> List[str]:
|
565
612
|
"""Identify performance bottlenecks from metrics."""
|
566
613
|
bottlenecks = []
|
567
|
-
|
614
|
+
|
568
615
|
# High iteration time variance suggests inconsistent performance
|
569
616
|
if metrics.iteration_time_stddev > metrics.avg_iteration_time * 0.5:
|
570
|
-
bottlenecks.append(
|
571
|
-
|
617
|
+
bottlenecks.append(
|
618
|
+
"High iteration time variance - inconsistent node performance"
|
619
|
+
)
|
620
|
+
|
572
621
|
# Very slow iterations
|
573
622
|
if metrics.max_iteration_time > metrics.avg_iteration_time * 3:
|
574
|
-
bottlenecks.append(
|
575
|
-
|
623
|
+
bottlenecks.append(
|
624
|
+
"Outlier slow iterations detected - potential resource contention"
|
625
|
+
)
|
626
|
+
|
576
627
|
# Memory bottlenecks
|
577
628
|
if metrics.memory_stats and metrics.memory_stats.get("max", 0) > 2000:
|
578
|
-
bottlenecks.append(
|
579
|
-
|
629
|
+
bottlenecks.append(
|
630
|
+
"High memory usage - potential memory leaks or inefficient data handling"
|
631
|
+
)
|
632
|
+
|
580
633
|
# CPU bottlenecks
|
581
634
|
if metrics.cpu_stats and metrics.cpu_stats.get("avg", 0) > 80:
|
582
635
|
bottlenecks.append("High CPU usage - computationally intensive operations")
|
583
|
-
|
636
|
+
|
584
637
|
return bottlenecks
|
585
|
-
|
638
|
+
|
586
639
|
def _identify_optimizations(self, metrics: PerformanceMetrics) -> List[str]:
|
587
640
|
"""Identify optimization opportunities."""
|
588
641
|
optimizations = []
|
589
|
-
|
642
|
+
|
590
643
|
# Low convergence rate suggests early termination opportunities
|
591
644
|
convergence_rate = metrics.convergence_stats.get("avg_convergence")
|
592
645
|
if convergence_rate and convergence_rate < 0.5:
|
593
|
-
optimizations.append(
|
594
|
-
|
646
|
+
optimizations.append(
|
647
|
+
"Add early termination conditions for faster convergence"
|
648
|
+
)
|
649
|
+
|
595
650
|
# High memory variance suggests optimization potential
|
596
651
|
if metrics.memory_stats and metrics.memory_stats.get("stddev", 0) > 100:
|
597
652
|
optimizations.append("Optimize memory usage patterns for consistency")
|
598
|
-
|
653
|
+
|
599
654
|
# Slow average iteration time
|
600
655
|
if metrics.avg_iteration_time > 0.1:
|
601
656
|
optimizations.append("Optimize node execution performance")
|
602
|
-
|
657
|
+
|
603
658
|
return optimizations
|
604
|
-
|
659
|
+
|
605
660
|
def _calculate_overall_score(self, metrics: PerformanceMetrics) -> float:
|
606
661
|
"""Calculate overall performance score (0-1, higher is better)."""
|
607
662
|
score_components = []
|
608
|
-
|
663
|
+
|
609
664
|
# Efficiency component (convergence rate)
|
610
665
|
if metrics.convergence_stats:
|
611
666
|
avg_convergence = metrics.convergence_stats.get("avg_convergence", 0.5)
|
612
667
|
score_components.append(min(1.0, avg_convergence))
|
613
|
-
|
668
|
+
|
614
669
|
# Speed component (based on iteration time)
|
615
670
|
if metrics.avg_iteration_time > 0:
|
616
671
|
speed_score = max(0.0, 1.0 - min(1.0, metrics.avg_iteration_time / 2.0))
|
617
672
|
score_components.append(speed_score)
|
618
|
-
|
673
|
+
|
619
674
|
# Consistency component (low variance is good)
|
620
675
|
if metrics.iteration_time_stddev >= 0:
|
621
|
-
consistency_score = max(
|
676
|
+
consistency_score = max(
|
677
|
+
0.0,
|
678
|
+
1.0
|
679
|
+
- min(
|
680
|
+
1.0,
|
681
|
+
(
|
682
|
+
metrics.iteration_time_stddev / metrics.avg_iteration_time
|
683
|
+
if metrics.avg_iteration_time > 0
|
684
|
+
else 0
|
685
|
+
),
|
686
|
+
),
|
687
|
+
)
|
622
688
|
score_components.append(consistency_score)
|
623
|
-
|
689
|
+
|
624
690
|
# Memory efficiency component
|
625
691
|
if metrics.memory_stats:
|
626
692
|
max_memory = metrics.memory_stats.get("max", 500)
|
627
|
-
memory_score = max(
|
693
|
+
memory_score = max(
|
694
|
+
0.0, 1.0 - min(1.0, max_memory / 2000)
|
695
|
+
) # Penalty after 2GB
|
628
696
|
score_components.append(memory_score)
|
629
|
-
|
697
|
+
|
630
698
|
return statistics.mean(score_components) if score_components else 0.5
|
631
|
-
|
699
|
+
|
632
700
|
def _analyze_performance_trends(self) -> Dict[str, Any]:
|
633
701
|
"""Analyze performance trends over time."""
|
634
702
|
if len(self.performance_history) < 2:
|
635
703
|
return {"trend": "insufficient_data"}
|
636
|
-
|
637
|
-
recent_scores = [
|
638
|
-
|
704
|
+
|
705
|
+
recent_scores = [
|
706
|
+
self._calculate_overall_score(m) for m in self.performance_history[-5:]
|
707
|
+
]
|
708
|
+
|
639
709
|
if len(recent_scores) >= 2:
|
640
710
|
trend = "improving" if recent_scores[-1] > recent_scores[0] else "degrading"
|
641
711
|
trend_strength = abs(recent_scores[-1] - recent_scores[0])
|
642
712
|
else:
|
643
713
|
trend = "stable"
|
644
714
|
trend_strength = 0.0
|
645
|
-
|
715
|
+
|
646
716
|
return {
|
647
717
|
"trend": trend,
|
648
718
|
"trend_strength": trend_strength,
|
649
719
|
"recent_scores": recent_scores,
|
650
|
-
"performance_history_length": len(self.performance_history)
|
720
|
+
"performance_history_length": len(self.performance_history),
|
651
721
|
}
|
652
|
-
|
722
|
+
|
653
723
|
def _get_cycle_comparisons(self) -> Dict[str, Any]:
|
654
724
|
"""Get comparative analysis across all cycles."""
|
655
725
|
if len(self.traces) < 2:
|
656
726
|
return {"comparison": "insufficient_data"}
|
657
|
-
|
727
|
+
|
658
728
|
cycle_scores = {}
|
659
729
|
for trace in self.traces:
|
660
730
|
stats = trace.get_statistics()
|
661
731
|
cycle_scores[trace.cycle_id] = stats["efficiency_score"]
|
662
|
-
|
732
|
+
|
663
733
|
best_cycle = max(cycle_scores.items(), key=lambda x: x[1])
|
664
734
|
worst_cycle = min(cycle_scores.items(), key=lambda x: x[1])
|
665
|
-
|
735
|
+
|
666
736
|
return {
|
667
737
|
"best_cycle": {"id": best_cycle[0], "score": best_cycle[1]},
|
668
738
|
"worst_cycle": {"id": worst_cycle[0], "score": worst_cycle[1]},
|
669
739
|
"score_range": best_cycle[1] - worst_cycle[1],
|
670
|
-
"total_cycles": len(cycle_scores)
|
671
|
-
}
|
740
|
+
"total_cycles": len(cycle_scores),
|
741
|
+
}
|