synapse-hub 2.1.0 → 2.2.0
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.
- package/.agents/agents/orchestrator.md +7 -0
- package/.agents/rules/lessons-learned.md +1 -0
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +1 -1
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +1 -2
- package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +11 -4
- package/.agents/skills/architecture-decision-records/SKILL.md +1 -1
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +32 -11
- package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/clean-pom-crafter/SKILL.md +1 -4
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +1 -1
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +1 -1
- package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +1 -3
- package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +1 -1
- package/.agents/skills/github-actions-templates/SKILL.md +1 -1
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +1 -3
- package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
- package/.agents/skills/lam-script-generator/SKILL.md +1 -2
- package/.agents/skills/playwright-live-interaction/SKILL.md +1 -4
- package/.agents/skills/security-dast-sentinel/SKILL.md +1 -1
- package/.agents/skills/self-healing-locator/SKILL.md +1 -2
- package/.agents/skills/skillspector-security-guard/SKILL.md +115 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +1 -1
- package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
- package/.agents/skills/visual-regression-sentinel/SKILL.md +1 -1
- package/.agents/skills/wcag-audit-patterns/SKILL.md +1 -1
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
- package/AGENTS.md +36 -0
- package/README.md +140 -103
- package/package.json +1 -1
- package/skills_manifest.json +42 -29
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +15 -1
- package/templates/ai_bridges/CLAUDE.md +31 -0
- package/templates/ai_bridges/copilot-instructions.md +25 -0
- package/templates/ai_bridges/cursor_synapse_qa.mdc +26 -0
- package/templates/ai_bridges/windsurfrules +25 -0
- package/templates/ci/github-actions-synapse.yml +28 -0
- package/templates/ci/gitlab-ci-synapse.yml +16 -0
- package/tools/TOOLS_RUNBOOK.md +203 -12
- package/tools/browser_helpers.js +2 -2
- package/tools/bump_version.py +8 -0
- package/tools/cli.py +74 -2
- package/tools/export_postman_collection.py +535 -218
- package/tools/generate_jmx_plan.py +672 -102
- package/tools/git_pre_commit_guard.py +21 -0
- package/tools/jtl_analyzer.py +474 -0
- package/tools/mcp_excel_server.ps1 +1 -1
- package/tools/mcp_server.py +1 -1
- package/tools/newman_analyzer.py +385 -0
- package/tools/setup_project.py +203 -18
- package/tools/skillspector.py +1141 -0
- package/tools/telemetry_correlator.py +351 -0
- package/tools/templates/jmeter-distributed/README.md +54 -0
- package/tools/templates/jmeter-distributed/docker-compose.yml +83 -0
- package/tools/update_sentinel.py +313 -0
- package/tools/repo_map.json +0 -12
- package/tools/repo_map.txt +0 -7
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
tools/telemetry_correlator.py — Full-Stack APM & Server Telemetry Performance Correlator
|
|
5
|
+
---------------------------------------------------------------------------------------
|
|
6
|
+
Correlates Client-Side perceived response times (from JMeter .jtl) with Server-Side
|
|
7
|
+
infrastructure metrics (Prometheus Node Exporter, PerfMon, CloudWatch, Datadog),
|
|
8
|
+
performs Pearson correlation analysis, isolates resource bottlenecks, and generates
|
|
9
|
+
an automated Root Cause Analysis (RCA) diagnostic report for Senior Performance QCs.
|
|
10
|
+
|
|
11
|
+
Capabilities:
|
|
12
|
+
1. Multi-Dimensional Correlation:
|
|
13
|
+
- Client Latency (P95/P99 ms) vs Server CPU Utilization (%)
|
|
14
|
+
- Client Latency vs Server Memory Utilization (%) / JVM GC Pauses
|
|
15
|
+
- Client Latency vs Database Connection Pool Saturation
|
|
16
|
+
- Client Latency vs Network I/O & Disk Write IOPS
|
|
17
|
+
2. Root Cause Attribution Engine:
|
|
18
|
+
- Detects CPU saturation (CPU > 85% correlating with latency spikes)
|
|
19
|
+
- Detects Memory leak / JVM Stop-the-world GC pauses (RAM > 90%)
|
|
20
|
+
- Detects Database connection starvation (DB Pool = Max)
|
|
21
|
+
- Detects Network / Gateway / Downstream third-party bottleneck
|
|
22
|
+
3. CI/CD Artifacts:
|
|
23
|
+
- Visual ANSI Correlation Matrix
|
|
24
|
+
- GitHub / GitLab Markdown RCA report with actionable tuning recommendations
|
|
25
|
+
- Structured JSON diagnostic export.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
python tools/telemetry_correlator.py --jtl results.jtl --server-metrics server.csv
|
|
29
|
+
python tools/telemetry_correlator.py --sample --markdown rca_report.md
|
|
30
|
+
agent-qa correlate-telemetry --jtl results.jtl --server-metrics server.csv
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
import csv
|
|
36
|
+
import json
|
|
37
|
+
import math
|
|
38
|
+
import argparse
|
|
39
|
+
from typing import Dict, List, Any, Optional, Tuple
|
|
40
|
+
|
|
41
|
+
# Enforce UTF-8 stdout encoding for Windows/macOS/Linux console compatibility
|
|
42
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
43
|
+
try:
|
|
44
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
45
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def calculate_pearson_correlation(x: List[float], y: List[float]) -> float:
|
|
51
|
+
"""Calculates Pearson correlation coefficient between two numeric series."""
|
|
52
|
+
n = min(len(x), len(y))
|
|
53
|
+
if n < 2:
|
|
54
|
+
return 0.0
|
|
55
|
+
|
|
56
|
+
x = x[:n]
|
|
57
|
+
y = y[:n]
|
|
58
|
+
|
|
59
|
+
mean_x = sum(x) / n
|
|
60
|
+
mean_y = sum(y) / n
|
|
61
|
+
|
|
62
|
+
numerator = sum((x[i] - mean_x) * (y[i] - mean_y) for i in range(n))
|
|
63
|
+
denom_x = sum((x[i] - mean_x) ** 2 for i in range(n))
|
|
64
|
+
denom_y = sum((y[i] - mean_y) ** 2 for i in range(n))
|
|
65
|
+
|
|
66
|
+
if denom_x <= 0 or denom_y <= 0:
|
|
67
|
+
return 0.0
|
|
68
|
+
|
|
69
|
+
return numerator / math.sqrt(denom_x * denom_y)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def parse_jtl_time_series(jtl_path: str, bucket_sec: int = 5) -> Dict[int, List[float]]:
|
|
73
|
+
"""Buckets JMeter elapsed times by N-second time windows."""
|
|
74
|
+
buckets: Dict[int, List[float]] = {}
|
|
75
|
+
with open(jtl_path, "r", encoding="utf-8", errors="replace") as f:
|
|
76
|
+
reader = csv.DictReader(f)
|
|
77
|
+
for row in reader:
|
|
78
|
+
try:
|
|
79
|
+
ts_ms = int(float(row.get("timeStamp") or row.get("Timestamp") or 0))
|
|
80
|
+
elapsed = float(row.get("elapsed") or row.get("Elapsed") or 0)
|
|
81
|
+
sec_bucket = (ts_ms // 1000 // bucket_sec) * bucket_sec
|
|
82
|
+
if sec_bucket not in buckets:
|
|
83
|
+
buckets[sec_bucket] = []
|
|
84
|
+
buckets[sec_bucket].append(elapsed)
|
|
85
|
+
except Exception:
|
|
86
|
+
continue
|
|
87
|
+
return buckets
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def parse_server_metrics(metrics_path: str, bucket_sec: int = 5) -> Dict[str, Dict[int, float]]:
|
|
91
|
+
"""
|
|
92
|
+
Parses server telemetry CSV (timestamp/time, cpu_pct, memory_pct, db_pool_pct, disk_iops).
|
|
93
|
+
"""
|
|
94
|
+
series: Dict[str, Dict[int, float]] = {
|
|
95
|
+
"cpu_pct": {},
|
|
96
|
+
"memory_pct": {},
|
|
97
|
+
"db_pool_pct": {},
|
|
98
|
+
"disk_iops": {}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
with open(metrics_path, "r", encoding="utf-8", errors="replace") as f:
|
|
102
|
+
reader = csv.DictReader(f)
|
|
103
|
+
for row in reader:
|
|
104
|
+
try:
|
|
105
|
+
# Timestamp can be unix epoch in seconds or milliseconds
|
|
106
|
+
raw_ts = float(row.get("timestamp") or row.get("time") or row.get("epoch") or 0)
|
|
107
|
+
ts_sec = int(raw_ts if raw_ts < 1e11 else raw_ts / 1000)
|
|
108
|
+
bucket = (ts_sec // bucket_sec) * bucket_sec
|
|
109
|
+
|
|
110
|
+
if "cpu" in row or "cpu_pct" in row:
|
|
111
|
+
series["cpu_pct"][bucket] = float(row.get("cpu_pct") or row.get("cpu") or 0)
|
|
112
|
+
if "memory" in row or "memory_pct" in row or "mem_pct" in row:
|
|
113
|
+
series["memory_pct"][bucket] = float(row.get("memory_pct") or row.get("memory") or 0)
|
|
114
|
+
if "db_pool" in row or "db_pool_pct" in row or "db_connections" in row:
|
|
115
|
+
series["db_pool_pct"][bucket] = float(row.get("db_pool_pct") or row.get("db_pool") or 0)
|
|
116
|
+
if "disk_iops" in row or "disk" in row or "iops" in row:
|
|
117
|
+
series["disk_iops"][bucket] = float(row.get("disk_iops") or row.get("disk") or 0)
|
|
118
|
+
except Exception:
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
return series
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def run_correlation_analysis(
|
|
125
|
+
jtl_buckets: Dict[int, List[float]],
|
|
126
|
+
server_series: Dict[str, Dict[int, float]]
|
|
127
|
+
) -> Dict[str, Any]:
|
|
128
|
+
"""Aligns time series and evaluates Pearson correlation & Root Cause."""
|
|
129
|
+
sorted_buckets = sorted(jtl_buckets.keys())
|
|
130
|
+
if not sorted_buckets:
|
|
131
|
+
return {"status": "NO_DATA", "correlations": {}, "root_causes": []}
|
|
132
|
+
|
|
133
|
+
# Calculate P95 per bucket
|
|
134
|
+
p95_series: List[float] = []
|
|
135
|
+
aligned_cpu: List[float] = []
|
|
136
|
+
aligned_mem: List[float] = []
|
|
137
|
+
aligned_db: List[float] = []
|
|
138
|
+
aligned_disk: List[float] = []
|
|
139
|
+
|
|
140
|
+
for b in sorted_buckets:
|
|
141
|
+
values = sorted(jtl_buckets[b])
|
|
142
|
+
p95 = values[int(len(values) * 0.95)] if values else 0.0
|
|
143
|
+
p95_series.append(p95)
|
|
144
|
+
|
|
145
|
+
aligned_cpu.append(server_series["cpu_pct"].get(b, 0.0))
|
|
146
|
+
aligned_mem.append(server_series["memory_pct"].get(b, 0.0))
|
|
147
|
+
aligned_db.append(server_series["db_pool_pct"].get(b, 0.0))
|
|
148
|
+
aligned_disk.append(server_series["disk_iops"].get(b, 0.0))
|
|
149
|
+
|
|
150
|
+
corr_cpu = calculate_pearson_correlation(p95_series, aligned_cpu)
|
|
151
|
+
corr_mem = calculate_pearson_correlation(p95_series, aligned_mem)
|
|
152
|
+
corr_db = calculate_pearson_correlation(p95_series, aligned_db)
|
|
153
|
+
corr_disk = calculate_pearson_correlation(p95_series, aligned_disk)
|
|
154
|
+
|
|
155
|
+
max_cpu = max(aligned_cpu) if aligned_cpu else 0
|
|
156
|
+
max_mem = max(aligned_mem) if aligned_mem else 0
|
|
157
|
+
max_db = max(aligned_db) if aligned_db else 0
|
|
158
|
+
max_p95 = max(p95_series) if p95_series else 0
|
|
159
|
+
|
|
160
|
+
root_causes: List[Dict[str, str]] = []
|
|
161
|
+
|
|
162
|
+
if corr_cpu >= 0.65 and max_cpu > 80:
|
|
163
|
+
root_causes.append({
|
|
164
|
+
"component": "Server CPU Compute",
|
|
165
|
+
"severity": "CRITICAL",
|
|
166
|
+
"correlation": f"{corr_cpu:.2f}",
|
|
167
|
+
"description": f"Strong positive correlation ({corr_cpu:.2f}) with peak CPU reaching {max_cpu:.1f}%. Compute capacity is saturated under peak load.",
|
|
168
|
+
"recommendation": "Profile hot methods/threads with async-profiler. Consider CPU thread pool tuning or vertical/horizontal pod autoscaling (HPA)."
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if corr_db >= 0.65 and max_db > 85:
|
|
172
|
+
root_causes.append({
|
|
173
|
+
"component": "Database Connection Pool",
|
|
174
|
+
"severity": "CRITICAL",
|
|
175
|
+
"correlation": f"{corr_db:.2f}",
|
|
176
|
+
"description": f"Strong positive correlation ({corr_db:.2f}) with DB Connection Pool reaching {max_db:.1f}% capacity. Threads are blocked waiting for pooled DB connections.",
|
|
177
|
+
"recommendation": "Increase HikariCP / Tomcat maxPoolSize. Audit slow SQL queries holding connections open or enable connection leak detection."
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
if corr_mem >= 0.60 and max_mem > 85:
|
|
181
|
+
root_causes.append({
|
|
182
|
+
"component": "Server Memory / JVM Heap",
|
|
183
|
+
"severity": "HIGH",
|
|
184
|
+
"correlation": f"{corr_mem:.2f}",
|
|
185
|
+
"description": f"Memory saturation reached {max_mem:.1f}% with correlation ({corr_mem:.2f}). Probable GC pause (Stop-The-World) throttling throughput.",
|
|
186
|
+
"recommendation": "Inspect GC logs (-Xlog:gc*). Tune JVM garbage collection flags (G1GC / ZGC) or adjust heap headroom (-Xmx)."
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
if not root_causes:
|
|
190
|
+
if max_p95 > 1000 and max_cpu < 50 and max_db < 50:
|
|
191
|
+
root_causes.append({
|
|
192
|
+
"component": "Downstream Network / External Dependency",
|
|
193
|
+
"severity": "MEDIUM",
|
|
194
|
+
"correlation": "N/A",
|
|
195
|
+
"description": f"Latency spiked to {max_p95:.1f}ms while Server CPU ({max_cpu:.1f}%) and DB Pool ({max_db:.1f}%) remained relaxed.",
|
|
196
|
+
"recommendation": "Audit external 3rd-party HTTP gateways, microservice RPC timeouts, or lock contention in synchronized code blocks."
|
|
197
|
+
})
|
|
198
|
+
else:
|
|
199
|
+
root_causes.append({
|
|
200
|
+
"component": "System Health Normal",
|
|
201
|
+
"severity": "INFO",
|
|
202
|
+
"correlation": "N/A",
|
|
203
|
+
"description": "Server hardware resources scale stably within acceptable performance boundaries.",
|
|
204
|
+
"recommendation": "Maintain current infrastructure capacity parameters."
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
"status": "COMPLETED",
|
|
209
|
+
"sample_points": len(sorted_buckets),
|
|
210
|
+
"metrics_summary": {
|
|
211
|
+
"max_p95_ms": round(max_p95, 2),
|
|
212
|
+
"max_cpu_pct": round(max_cpu, 2),
|
|
213
|
+
"max_memory_pct": round(max_mem, 2),
|
|
214
|
+
"max_db_pool_pct": round(max_db, 2),
|
|
215
|
+
},
|
|
216
|
+
"correlations": {
|
|
217
|
+
"cpu_correlation": round(corr_cpu, 3),
|
|
218
|
+
"memory_correlation": round(corr_mem, 3),
|
|
219
|
+
"db_pool_correlation": round(corr_db, 3),
|
|
220
|
+
"disk_correlation": round(corr_disk, 3)
|
|
221
|
+
},
|
|
222
|
+
"root_causes": root_causes
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def format_rca_markdown(analysis: Dict[str, Any]) -> str:
|
|
227
|
+
"""Generates GitHub / GitLab Markdown RCA report."""
|
|
228
|
+
summary = analysis["metrics_summary"]
|
|
229
|
+
corrs = analysis["correlations"]
|
|
230
|
+
causes = analysis["root_causes"]
|
|
231
|
+
|
|
232
|
+
lines = [
|
|
233
|
+
"# 🔬 Full-Stack Performance Telemetry Correlation & RCA Report",
|
|
234
|
+
"",
|
|
235
|
+
"### 📊 Telemetry Metrics Summary",
|
|
236
|
+
f"- **Max Client P95 Latency:** `{summary['max_p95_ms']} ms`",
|
|
237
|
+
f"- **Peak Server CPU Utilization:** `{summary['max_cpu_pct']}%`",
|
|
238
|
+
f"- **Peak Memory Consumption:** `{summary['max_memory_pct']}%`",
|
|
239
|
+
f"- **Peak Database Pool Usage:** `{summary['max_db_pool_pct']}%`",
|
|
240
|
+
"",
|
|
241
|
+
"### 🔗 Pearson Correlation Matrix (Client Latency vs Server Telemetry)",
|
|
242
|
+
"| Server Metric | Pearson Coefficient ($r$) | Impact Level | Interpretation |",
|
|
243
|
+
"| :--- | :---: | :---: | :--- |",
|
|
244
|
+
f"| **CPU Utilization** | `{corrs['cpu_correlation']}` | {'🔴 HIGH' if corrs['cpu_correlation'] > 0.6 else '🟢 LOW'} | Latency tracks CPU load directly |",
|
|
245
|
+
f"| **Database Pool** | `{corrs['db_pool_correlation']}` | {'🔴 HIGH' if corrs['db_pool_correlation'] > 0.6 else '🟢 LOW'} | Connection pool saturation bottleneck |",
|
|
246
|
+
f"| **Memory / JVM** | `{corrs['memory_correlation']}` | {'🟠 MEDIUM' if corrs['memory_correlation'] > 0.5 else '🟢 LOW'} | Possible GC pause or memory pressure |",
|
|
247
|
+
f"| **Disk I/O** | `{corrs['disk_correlation']}` | {'🟠 MEDIUM' if corrs['disk_correlation'] > 0.5 else '🟢 LOW'} | Storage latency / write queueing |",
|
|
248
|
+
"",
|
|
249
|
+
"### 🎯 Root Cause Attribution & Engineering Action Items",
|
|
250
|
+
""
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
for c in causes:
|
|
254
|
+
badge = "🔴 CRITICAL" if c["severity"] == "CRITICAL" else ("🟠 HIGH" if c["severity"] == "HIGH" else "🔵 INFO")
|
|
255
|
+
lines.extend([
|
|
256
|
+
f"#### [{badge}] {c['component']}",
|
|
257
|
+
f"- **Observation:** {c['description']}",
|
|
258
|
+
f"- **Actionable Tuning:** {c['recommendation']}",
|
|
259
|
+
""
|
|
260
|
+
])
|
|
261
|
+
|
|
262
|
+
lines.extend([
|
|
263
|
+
"---",
|
|
264
|
+
"*Report auto-generated by Synapse-Hub `telemetry_correlator.py` - Enterprise Observability Suite.*"
|
|
265
|
+
])
|
|
266
|
+
return "\n".join(lines)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def main():
|
|
270
|
+
parser = argparse.ArgumentParser(
|
|
271
|
+
description="Full-Stack APM & Server Telemetry Performance Correlator (Senior QC Performance Suite)."
|
|
272
|
+
)
|
|
273
|
+
parser.add_argument("--jtl", help="Path to JMeter results file (.jtl).")
|
|
274
|
+
parser.add_argument("--server-metrics", "-s", help="Path to Server Telemetry CSV (timestamp, cpu_pct, memory_pct, db_pool_pct).")
|
|
275
|
+
parser.add_argument("--markdown", "-m", help="Path to export Markdown RCA report.")
|
|
276
|
+
parser.add_argument("--json", "-j", help="Path to export structured JSON diagnostics.")
|
|
277
|
+
parser.add_argument("--sample", action="store_true", help="Generate synthetic client & server telemetry dataset for verification.")
|
|
278
|
+
|
|
279
|
+
args = parser.parse_args()
|
|
280
|
+
|
|
281
|
+
if args.sample or not args.jtl or not args.server_metrics:
|
|
282
|
+
import tempfile, time
|
|
283
|
+
temp_dir = tempfile.gettempdir()
|
|
284
|
+
temp_jtl = os.path.join(temp_dir, "sample_corr_jmeter.jtl")
|
|
285
|
+
temp_srv = os.path.join(temp_dir, "sample_corr_server.csv")
|
|
286
|
+
|
|
287
|
+
base_ts = int(time.time() * 1000)
|
|
288
|
+
# Create synthetic correlated dataset: CPU and Latency spike together in the middle
|
|
289
|
+
with open(temp_jtl, "w", encoding="utf-8", newline="") as f:
|
|
290
|
+
w = csv.writer(f)
|
|
291
|
+
w.writerow(["timeStamp", "elapsed", "label", "responseCode", "success"])
|
|
292
|
+
for sec in range(60):
|
|
293
|
+
# Spike between sec 20 and 40
|
|
294
|
+
is_spike = 20 <= sec <= 40
|
|
295
|
+
elapsed = 150 + (1200 if is_spike else 0) + (sec % 10)
|
|
296
|
+
for _ in range(10):
|
|
297
|
+
w.writerow([base_ts + (sec * 1000), elapsed, "[POST] /api/v1/checkout", "200", "true"])
|
|
298
|
+
|
|
299
|
+
with open(temp_srv, "w", encoding="utf-8", newline="") as f:
|
|
300
|
+
w = csv.writer(f)
|
|
301
|
+
w.writerow(["timestamp", "cpu_pct", "memory_pct", "db_pool_pct", "disk_iops"])
|
|
302
|
+
for sec in range(60):
|
|
303
|
+
is_spike = 20 <= sec <= 40
|
|
304
|
+
cpu = 25.0 + (65.0 if is_spike else 5.0) + (sec % 3)
|
|
305
|
+
mem = 60.0 + (sec * 0.2)
|
|
306
|
+
db = 30.0 + (60.0 if is_spike else 0.0)
|
|
307
|
+
w.writerow([int(base_ts / 1000) + sec, cpu, mem, db, 120])
|
|
308
|
+
|
|
309
|
+
args.jtl = temp_jtl
|
|
310
|
+
args.server_metrics = temp_srv
|
|
311
|
+
print(f"🔬 [SAMPLE MODE] Generated synthetic correlated dataset in {temp_dir}")
|
|
312
|
+
|
|
313
|
+
jtl_buckets = parse_jtl_time_series(args.jtl, bucket_sec=5)
|
|
314
|
+
server_series = parse_server_metrics(args.server_metrics, bucket_sec=5)
|
|
315
|
+
|
|
316
|
+
analysis = run_correlation_analysis(jtl_buckets, server_series)
|
|
317
|
+
|
|
318
|
+
print("\n" + "=" * 80)
|
|
319
|
+
print(" FULL-STACK APM & SERVER TELEMETRY CORRELATION ENGINE (RCA MATRIX)")
|
|
320
|
+
print("=" * 80)
|
|
321
|
+
print(f"Sample Windows Evaluated : {analysis['sample_points']} time slices (5s interval)")
|
|
322
|
+
print(f"Peak Client P95 Latency : {analysis['metrics_summary']['max_p95_ms']} ms")
|
|
323
|
+
print(f"Peak Server CPU : {analysis['metrics_summary']['max_cpu_pct']}%")
|
|
324
|
+
print(f"Peak Database Pool : {analysis['metrics_summary']['max_db_pool_pct']}%")
|
|
325
|
+
print("-" * 80)
|
|
326
|
+
print("Pearson Correlation Coefficients (r):")
|
|
327
|
+
for k, v in analysis["correlations"].items():
|
|
328
|
+
print(f" • {k.replace('_', ' ').title():<25} : {v:+0.3f}")
|
|
329
|
+
print("-" * 80)
|
|
330
|
+
print("🎯 Root Cause Analysis (RCA):")
|
|
331
|
+
for c in analysis["root_causes"]:
|
|
332
|
+
print(f" [{c['severity']}] {c['component']}: {c['description']}")
|
|
333
|
+
print(f" 👉 Recommendation: {c['recommendation']}")
|
|
334
|
+
print("=" * 80 + "\n")
|
|
335
|
+
|
|
336
|
+
if args.markdown:
|
|
337
|
+
md = format_rca_markdown(analysis)
|
|
338
|
+
os.makedirs(os.path.dirname(os.path.abspath(args.markdown)) or ".", exist_ok=True)
|
|
339
|
+
with open(args.markdown, "w", encoding="utf-8") as f:
|
|
340
|
+
f.write(md)
|
|
341
|
+
print(f"📄 Markdown RCA Report exported: {args.markdown}")
|
|
342
|
+
|
|
343
|
+
if args.json:
|
|
344
|
+
os.makedirs(os.path.dirname(os.path.abspath(args.json)) or ".", exist_ok=True)
|
|
345
|
+
with open(args.json, "w", encoding="utf-8") as f:
|
|
346
|
+
json.dump(analysis, f, indent=2)
|
|
347
|
+
print(f"📊 JSON Diagnostics exported: {args.json}")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
if __name__ == "__main__":
|
|
351
|
+
main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Enterprise Apache JMeter Distributed Load Testing Architecture
|
|
2
|
+
|
|
3
|
+
This directory provides a 1-click Dockerized distributed JMeter cluster for injecting high-concurrency loads (> 5,000–50,000 VUs) across multiple injection nodes.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture Overview
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
+-------------------+
|
|
11
|
+
| jmeter-master | (Controller)
|
|
12
|
+
+---------+---------+
|
|
13
|
+
| RMI (Non-SSL)
|
|
14
|
+
+-----------------------+-----------------------+
|
|
15
|
+
| | |
|
|
16
|
+
+-------v-------+ +-------v-------+ +-------v-------+
|
|
17
|
+
| jmeter-slave-1| | jmeter-slave-2| | jmeter-slave-3|
|
|
18
|
+
| (1,000 VUs) | | (1,000 VUs) | | (1,000 VUs) |
|
|
19
|
+
+-------+-------+ +-------+-------+ +-------+-------+
|
|
20
|
+
| | |
|
|
21
|
+
+-----------------------+-----------------------+
|
|
22
|
+
|
|
|
23
|
+
[Target System]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Start (3 Steps)
|
|
29
|
+
|
|
30
|
+
### 1. Start the Cluster
|
|
31
|
+
```bash
|
|
32
|
+
docker-compose up -d
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Run Distributed Headless Execution from Master
|
|
36
|
+
Copy your test plan (generated by Synapse-Hub `agent-qa generate-jmx`) into `./plans/perf_plan.jmx`, then trigger:
|
|
37
|
+
```bash
|
|
38
|
+
docker exec -it jmeter-master jmeter -n \
|
|
39
|
+
-t /jmeter/plans/perf_plan.jmx \
|
|
40
|
+
-R jmeter-slave-1,jmeter-slave-2,jmeter-slave-3 \
|
|
41
|
+
-Dserver.rmi.ssl.disable=true \
|
|
42
|
+
-l /jmeter/reports/distributed_results.jtl \
|
|
43
|
+
-e -o /jmeter/reports/dashboard
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Evaluate SLA with Synapse-Hub Quality Gate
|
|
47
|
+
```bash
|
|
48
|
+
agent-qa analyze-jtl ./reports/distributed_results.jtl --sla-p95 500 --max-error-rate 1.0
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 4. Teardown
|
|
52
|
+
```bash
|
|
53
|
+
docker-compose down -v
|
|
54
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
# Apache JMeter 5.x Distributed Load Testing Cluster (Master + 3 Slaves)
|
|
5
|
+
# Auto-provisioned by Synapse-Hub Performance Suite
|
|
6
|
+
# ==============================================================================
|
|
7
|
+
|
|
8
|
+
services:
|
|
9
|
+
# ----------------------------------------------------------------------------
|
|
10
|
+
# Slave Injector Node 1
|
|
11
|
+
# ----------------------------------------------------------------------------
|
|
12
|
+
jmeter-slave-1:
|
|
13
|
+
image: justb4/jmeter:5.5
|
|
14
|
+
container_name: jmeter-slave-1
|
|
15
|
+
command: >
|
|
16
|
+
-n -s
|
|
17
|
+
-Dserver.rmi.ssl.disable=true
|
|
18
|
+
-Dserver_port=1099
|
|
19
|
+
-Dserver.rmi.localport=50000
|
|
20
|
+
expose:
|
|
21
|
+
- "1099"
|
|
22
|
+
- "50000"
|
|
23
|
+
networks:
|
|
24
|
+
- jmeter-cluster
|
|
25
|
+
|
|
26
|
+
# ----------------------------------------------------------------------------
|
|
27
|
+
# Slave Injector Node 2
|
|
28
|
+
# ----------------------------------------------------------------------------
|
|
29
|
+
jmeter-slave-2:
|
|
30
|
+
image: justb4/jmeter:5.5
|
|
31
|
+
container_name: jmeter-slave-2
|
|
32
|
+
command: >
|
|
33
|
+
-n -s
|
|
34
|
+
-Dserver.rmi.ssl.disable=true
|
|
35
|
+
-Dserver_port=1099
|
|
36
|
+
-Dserver.rmi.localport=50000
|
|
37
|
+
expose:
|
|
38
|
+
- "1099"
|
|
39
|
+
- "50000"
|
|
40
|
+
networks:
|
|
41
|
+
- jmeter-cluster
|
|
42
|
+
|
|
43
|
+
# ----------------------------------------------------------------------------
|
|
44
|
+
# Slave Injector Node 3
|
|
45
|
+
# ----------------------------------------------------------------------------
|
|
46
|
+
jmeter-slave-3:
|
|
47
|
+
image: justb4/jmeter:5.5
|
|
48
|
+
container_name: jmeter-slave-3
|
|
49
|
+
command: >
|
|
50
|
+
-n -s
|
|
51
|
+
-Dserver.rmi.ssl.disable=true
|
|
52
|
+
-Dserver_port=1099
|
|
53
|
+
-Dserver.rmi.localport=50000
|
|
54
|
+
expose:
|
|
55
|
+
- "1099"
|
|
56
|
+
- "50000"
|
|
57
|
+
networks:
|
|
58
|
+
- jmeter-cluster
|
|
59
|
+
|
|
60
|
+
# ----------------------------------------------------------------------------
|
|
61
|
+
# Master Controller Node
|
|
62
|
+
# ----------------------------------------------------------------------------
|
|
63
|
+
jmeter-master:
|
|
64
|
+
image: justb4/jmeter:5.5
|
|
65
|
+
container_name: jmeter-master
|
|
66
|
+
depends_on:
|
|
67
|
+
- jmeter-slave-1
|
|
68
|
+
- jmeter-slave-2
|
|
69
|
+
- jmeter-slave-3
|
|
70
|
+
volumes:
|
|
71
|
+
- ./plans:/jmeter/plans
|
|
72
|
+
- ./reports:/jmeter/reports
|
|
73
|
+
environment:
|
|
74
|
+
- JVM_ARGS="-Xms1g -Xmx2g"
|
|
75
|
+
entrypoint: ["/bin/sh", "-c"]
|
|
76
|
+
command: >
|
|
77
|
+
"tail -f /dev/null"
|
|
78
|
+
networks:
|
|
79
|
+
- jmeter-cluster
|
|
80
|
+
|
|
81
|
+
networks:
|
|
82
|
+
jmeter-cluster:
|
|
83
|
+
driver: bridge
|