@intentsolutionsio/performance-test-suite 1.0.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/.claude-plugin/plugin.json +19 -0
- package/LICENSE +21 -0
- package/README.md +282 -0
- package/agents/performance-tester.md +296 -0
- package/package.json +40 -0
- package/skills/running-performance-tests/SKILL.md +140 -0
- package/skills/running-performance-tests/assets/README.md +7 -0
- package/skills/running-performance-tests/assets/example_test_configurations.json +121 -0
- package/skills/running-performance-tests/assets/report_template.html +203 -0
- package/skills/running-performance-tests/assets/test_template.js +65 -0
- package/skills/running-performance-tests/references/README.md +4 -0
- package/skills/running-performance-tests/scripts/README.md +11 -0
- package/skills/running-performance-tests/scripts/analyze_results.py +134 -0
- package/skills/running-performance-tests/scripts/init_test.py +94 -0
- package/skills/running-performance-tests/scripts/run_test.sh +100 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: running-performance-tests
|
|
3
|
+
description: |
|
|
4
|
+
Execute load testing, stress testing, and performance benchmarking.
|
|
5
|
+
Use when performing specialized testing.
|
|
6
|
+
Trigger with phrases like "run load tests", "test performance", or "benchmark the system".
|
|
7
|
+
|
|
8
|
+
allowed-tools: Read, Write, Edit, Grep, Glob, Bash(test:perf-*)
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
11
|
+
license: MIT
|
|
12
|
+
compatible-with: claude-code, codex, openclaw
|
|
13
|
+
tags: [testing, performance, performance-tests]
|
|
14
|
+
---
|
|
15
|
+
# Performance Test Suite
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
Execute load testing, stress testing, and performance benchmarking to identify bottlenecks, establish baseline metrics, and verify SLA compliance. Supports k6 (recommended), Artillery, Apache JMeter, Locust (Python), and autocannon (Node.js).
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
- Performance testing tool installed (`k6`, `artillery`, `locust`, `jmeter`, or `autocannon`)
|
|
24
|
+
- Target application deployed in a production-like environment (not local dev)
|
|
25
|
+
- Baseline performance metrics or SLA targets (e.g., p95 < 200ms, 99.9% availability)
|
|
26
|
+
- Monitoring stack accessible (Grafana, CloudWatch, Datadog) for resource metrics during tests
|
|
27
|
+
- Test data sufficient to avoid cache-only responses
|
|
28
|
+
|
|
29
|
+
## Instructions
|
|
30
|
+
|
|
31
|
+
1. Define performance test scenarios based on production traffic patterns:
|
|
32
|
+
- **Load test**: Simulate expected peak traffic (e.g., 500 concurrent users for 10 minutes).
|
|
33
|
+
- **Stress test**: Ramp beyond expected capacity to find the breaking point.
|
|
34
|
+
- **Spike test**: Sudden burst of traffic (0 to 1000 users in 10 seconds).
|
|
35
|
+
- **Soak test**: Sustained moderate load for extended duration (1-4 hours) to detect memory leaks.
|
|
36
|
+
2. Create test scripts targeting critical endpoints:
|
|
37
|
+
- Identify the top 5-10 most-hit API endpoints from production access logs.
|
|
38
|
+
- Include both read (GET) and write (POST/PUT/DELETE) operations.
|
|
39
|
+
- Simulate realistic user behavior with think time between requests.
|
|
40
|
+
- Use parameterized data to avoid cache-only hits (randomize query parameters, user IDs).
|
|
41
|
+
3. Configure load profiles:
|
|
42
|
+
- Define virtual user (VU) ramp-up stages (e.g., 10 VUs for 1 minute, then 50 VUs for 5 minutes).
|
|
43
|
+
- Set test duration appropriate to the scenario (load: 10-15 min, soak: 1-4 hours).
|
|
44
|
+
- Configure request timeouts matching production settings.
|
|
45
|
+
4. Execute the performance test:
|
|
46
|
+
- Run from a machine with sufficient network bandwidth and CPU.
|
|
47
|
+
- Avoid running from the same host as the application under test.
|
|
48
|
+
- Monitor application metrics (CPU, memory, DB connections) during execution.
|
|
49
|
+
5. Analyze results against SLA thresholds:
|
|
50
|
+
- p50, p90, p95, p99 response times.
|
|
51
|
+
- Requests per second (throughput).
|
|
52
|
+
- Error rate (target: < 0.1% for load test, higher tolerance for stress test).
|
|
53
|
+
- Resource utilization (CPU < 80%, memory < 85% at peak load).
|
|
54
|
+
6. Identify and document bottlenecks:
|
|
55
|
+
- Slow database queries (check slow query logs).
|
|
56
|
+
- CPU-bound operations (profiling data).
|
|
57
|
+
- Memory leaks (growing RSS over soak test).
|
|
58
|
+
- Connection pool exhaustion (database or HTTP client).
|
|
59
|
+
7. Generate a performance report with visualizations and recommendations.
|
|
60
|
+
|
|
61
|
+
## Output
|
|
62
|
+
|
|
63
|
+
- Performance test scripts (k6 `.js`, Artillery `.yml`, or Locust `.py` files)
|
|
64
|
+
- Execution results with response time percentiles, throughput, and error rates
|
|
65
|
+
- Performance report comparing results against SLA thresholds
|
|
66
|
+
- Bottleneck analysis with specific recommendations
|
|
67
|
+
- CI integration configuration for automated performance regression detection
|
|
68
|
+
|
|
69
|
+
## Error Handling
|
|
70
|
+
|
|
71
|
+
| Error | Cause | Solution |
|
|
72
|
+
|-------|-------|---------|
|
|
73
|
+
| Connection reset by peer | Server or load balancer dropping connections under load | Check max connections settings; increase connection pool size; verify keep-alive configuration |
|
|
74
|
+
| Timeouts spike at certain VU count | Application thread pool or database connection pool exhausted | Profile connection usage; increase pool size; add connection queuing; optimize slow queries |
|
|
75
|
+
| Inconsistent results between runs | Cache warming, garbage collection pauses, or noisy neighbor effects | Run a warm-up phase before measurement; use dedicated test infrastructure; average across 3 runs |
|
|
76
|
+
| Load generator CPU maxed out | Single machine cannot generate sufficient load | Distribute load generation across multiple machines; use cloud-based load generation services |
|
|
77
|
+
| All requests return cached responses | Test data not sufficiently varied | Randomize request parameters; use unique IDs per request; disable CDN caching for test environment |
|
|
78
|
+
|
|
79
|
+
## Examples
|
|
80
|
+
|
|
81
|
+
**k6 load test script:**
|
|
82
|
+
```javascript
|
|
83
|
+
import http from 'k6/http';
|
|
84
|
+
import { check, sleep } from 'k6';
|
|
85
|
+
|
|
86
|
+
export const options = {
|
|
87
|
+
stages: [
|
|
88
|
+
{ duration: '2m', target: 50 }, // Ramp up
|
|
89
|
+
{ duration: '5m', target: 50 }, // Sustained load
|
|
90
|
+
{ duration: '2m', target: 200 }, // Stress # HTTP 200 OK
|
|
91
|
+
{ duration: '1m', target: 0 }, // Ramp down
|
|
92
|
+
],
|
|
93
|
+
thresholds: {
|
|
94
|
+
http_req_duration: ['p(95)<200', 'p(99)<500'], # 500: HTTP 200 OK
|
|
95
|
+
http_req_failed: ['rate<0.01'],
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default function () {
|
|
100
|
+
const res = http.get('https://api.test.com/products');
|
|
101
|
+
check(res, {
|
|
102
|
+
'status is 200': (r) => r.status === 200, # HTTP 200 OK
|
|
103
|
+
'response time OK': (r) => r.timings.duration < 300, # 300: timeout: 5 minutes
|
|
104
|
+
});
|
|
105
|
+
sleep(1); // Think time
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Artillery test configuration:**
|
|
110
|
+
```yaml
|
|
111
|
+
config:
|
|
112
|
+
target: "https://api.test.com"
|
|
113
|
+
phases:
|
|
114
|
+
- duration: 120
|
|
115
|
+
arrivalRate: 10
|
|
116
|
+
name: "Warm up"
|
|
117
|
+
- duration: 300 # 300: timeout: 5 minutes
|
|
118
|
+
arrivalRate: 50
|
|
119
|
+
name: "Sustained load"
|
|
120
|
+
ensure:
|
|
121
|
+
p95: 200 # HTTP 200 OK
|
|
122
|
+
maxErrorRate: 1
|
|
123
|
+
scenarios:
|
|
124
|
+
- flow:
|
|
125
|
+
- get:
|
|
126
|
+
url: "/api/products"
|
|
127
|
+
- think: 1
|
|
128
|
+
- post:
|
|
129
|
+
url: "/api/cart"
|
|
130
|
+
json: { productId: "{{ $randomString() }}" }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Resources
|
|
134
|
+
|
|
135
|
+
- k6 documentation: https://grafana.com/docs/k6/latest/
|
|
136
|
+
- Artillery: https://www.artillery.io/docs
|
|
137
|
+
- Locust (Python): https://docs.locust.io/
|
|
138
|
+
- Apache JMeter: https://jmeter.apache.org/
|
|
139
|
+
- autocannon (Node.js): https://github.com/mcollina/autocannon
|
|
140
|
+
- Performance testing best practices: https://grafana.com/blog/2024/01/30/load-testing-best-practices/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Assets
|
|
2
|
+
|
|
3
|
+
Bundled resources for performance-test-suite skill
|
|
4
|
+
|
|
5
|
+
- [ ] test_template.js: Template file for creating k6 performance tests.
|
|
6
|
+
- [ ] report_template.html: HTML template for generating performance test reports.
|
|
7
|
+
- [ ] example_test_configurations.json: Example configurations for different types of performance tests.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"_comment": "Example configuration for a basic load test with gradual ramp-up",
|
|
4
|
+
"test_name": "Basic Load Test - Ramp Up",
|
|
5
|
+
"test_type": "load",
|
|
6
|
+
"description": "Simulates a gradual increase in users to test system responsiveness under increasing load.",
|
|
7
|
+
"target_url": "https://example.com/api/users",
|
|
8
|
+
"http_method": "GET",
|
|
9
|
+
"num_users": 100,
|
|
10
|
+
"ramp_up_time": 60,
|
|
11
|
+
"duration": 300,
|
|
12
|
+
"request_body": null,
|
|
13
|
+
"headers": {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
"Accept": "application/json"
|
|
16
|
+
},
|
|
17
|
+
"assertions": [
|
|
18
|
+
{
|
|
19
|
+
"type": "status_code",
|
|
20
|
+
"value": 200
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"type": "response_time",
|
|
24
|
+
"max_ms": 500
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"metrics": [
|
|
28
|
+
"response_time",
|
|
29
|
+
"requests_per_second",
|
|
30
|
+
"error_rate"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"_comment": "Example configuration for a stress test to find the breaking point",
|
|
35
|
+
"test_name": "Stress Test - Breaking Point",
|
|
36
|
+
"test_type": "stress",
|
|
37
|
+
"description": "Gradually increases load until the system reaches its breaking point.",
|
|
38
|
+
"target_url": "https://example.com/api/products",
|
|
39
|
+
"http_method": "POST",
|
|
40
|
+
"num_users": 500,
|
|
41
|
+
"ramp_up_time": 120,
|
|
42
|
+
"duration": 600,
|
|
43
|
+
"request_body": {
|
|
44
|
+
"product_name": "Test Product",
|
|
45
|
+
"price": 99.99
|
|
46
|
+
},
|
|
47
|
+
"headers": {
|
|
48
|
+
"Content-Type": "application/json"
|
|
49
|
+
},
|
|
50
|
+
"assertions": [
|
|
51
|
+
{
|
|
52
|
+
"type": "status_code",
|
|
53
|
+
"value": 201
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"metrics": [
|
|
57
|
+
"cpu_usage",
|
|
58
|
+
"memory_usage",
|
|
59
|
+
"database_connections",
|
|
60
|
+
"error_rate"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"_comment": "Example configuration for a spike test simulating a flash sale",
|
|
65
|
+
"test_name": "Spike Test - Flash Sale",
|
|
66
|
+
"test_type": "spike",
|
|
67
|
+
"description": "Simulates a sudden surge in traffic to test system resilience.",
|
|
68
|
+
"target_url": "https://example.com/api/orders",
|
|
69
|
+
"http_method": "POST",
|
|
70
|
+
"num_users": 2000,
|
|
71
|
+
"ramp_up_time": 10,
|
|
72
|
+
"duration": 60,
|
|
73
|
+
"request_body": {
|
|
74
|
+
"user_id": 123,
|
|
75
|
+
"product_id": 456,
|
|
76
|
+
"quantity": 1
|
|
77
|
+
},
|
|
78
|
+
"headers": {
|
|
79
|
+
"Content-Type": "application/json"
|
|
80
|
+
},
|
|
81
|
+
"assertions": [
|
|
82
|
+
{
|
|
83
|
+
"type": "status_code",
|
|
84
|
+
"value": 201
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"metrics": [
|
|
88
|
+
"response_time",
|
|
89
|
+
"requests_per_second",
|
|
90
|
+
"error_rate",
|
|
91
|
+
"database_queue_length"
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"_comment": "Example configuration for an endurance test to check for memory leaks",
|
|
96
|
+
"test_name": "Endurance Test - Memory Leak Check",
|
|
97
|
+
"test_type": "endurance",
|
|
98
|
+
"description": "Runs a sustained load for an extended period to detect memory leaks and performance degradation.",
|
|
99
|
+
"target_url": "https://example.com/api/data",
|
|
100
|
+
"http_method": "GET",
|
|
101
|
+
"num_users": 50,
|
|
102
|
+
"ramp_up_time": 30,
|
|
103
|
+
"duration": 3600,
|
|
104
|
+
"request_body": null,
|
|
105
|
+
"headers": {
|
|
106
|
+
"Authorization": "Bearer <your_api_key>"
|
|
107
|
+
},
|
|
108
|
+
"assertions": [
|
|
109
|
+
{
|
|
110
|
+
"type": "status_code",
|
|
111
|
+
"value": 200
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"metrics": [
|
|
115
|
+
"memory_usage",
|
|
116
|
+
"cpu_usage",
|
|
117
|
+
"response_time",
|
|
118
|
+
"garbage_collection_count"
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
]
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Performance Test Report</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* Inline CSS for styling */
|
|
9
|
+
body {
|
|
10
|
+
font-family: sans-serif;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
background-color: #f4f4f4;
|
|
14
|
+
color: #333;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.container {
|
|
18
|
+
width: 90%;
|
|
19
|
+
max-width: 1200px;
|
|
20
|
+
margin: 20px auto;
|
|
21
|
+
background-color: #fff;
|
|
22
|
+
padding: 20px;
|
|
23
|
+
border-radius: 8px;
|
|
24
|
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
h1, h2, h3 {
|
|
28
|
+
color: #0056b3;
|
|
29
|
+
margin-bottom: 10px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
table {
|
|
33
|
+
width: 100%;
|
|
34
|
+
border-collapse: collapse;
|
|
35
|
+
margin-top: 20px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
th, td {
|
|
39
|
+
padding: 10px;
|
|
40
|
+
text-align: left;
|
|
41
|
+
border-bottom: 1px solid #ddd;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
th {
|
|
45
|
+
background-color: #f2f2f2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.summary {
|
|
49
|
+
margin-top: 20px;
|
|
50
|
+
padding: 15px;
|
|
51
|
+
background-color: #e9ecef;
|
|
52
|
+
border-radius: 5px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.summary p {
|
|
56
|
+
margin: 5px 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.charts {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-wrap: wrap;
|
|
62
|
+
justify-content: space-around;
|
|
63
|
+
margin-top: 20px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.chart {
|
|
67
|
+
width: 45%;
|
|
68
|
+
min-width: 300px;
|
|
69
|
+
margin-bottom: 20px;
|
|
70
|
+
border: 1px solid #ddd;
|
|
71
|
+
padding: 10px;
|
|
72
|
+
border-radius: 5px;
|
|
73
|
+
box-sizing: border-box; /* Important for padding */
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.chart img {
|
|
77
|
+
max-width: 100%;
|
|
78
|
+
height: auto;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@media (max-width: 768px) {
|
|
82
|
+
.container {
|
|
83
|
+
width: 95%;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.chart {
|
|
87
|
+
width: 100%;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.bottlenecks {
|
|
92
|
+
margin-top: 20px;
|
|
93
|
+
padding: 15px;
|
|
94
|
+
background-color: #ffebee;
|
|
95
|
+
border-radius: 5px;
|
|
96
|
+
border: 1px solid #ef9a9a;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.bottlenecks h3 {
|
|
100
|
+
color: #d32f2f;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.recommendations {
|
|
104
|
+
margin-top: 20px;
|
|
105
|
+
padding: 15px;
|
|
106
|
+
background-color: #e8f5e9;
|
|
107
|
+
border-radius: 5px;
|
|
108
|
+
border: 1px solid #a5d6a7;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.recommendations h3 {
|
|
112
|
+
color: #388e3c;
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
115
|
+
</head>
|
|
116
|
+
<body>
|
|
117
|
+
|
|
118
|
+
<!-- Main Container -->
|
|
119
|
+
<div class="container">
|
|
120
|
+
|
|
121
|
+
<!-- Report Header -->
|
|
122
|
+
<h1>Performance Test Report</h1>
|
|
123
|
+
<p>Test Run: {{test_run_id}}</p>
|
|
124
|
+
<p>Date: {{report_date}}</p>
|
|
125
|
+
|
|
126
|
+
<!-- Test Summary -->
|
|
127
|
+
<div class="summary">
|
|
128
|
+
<h2>Test Summary</h2>
|
|
129
|
+
<p><strong>Test Type:</strong> {{test_type}}</p>
|
|
130
|
+
<p><strong>Target URL:</strong> {{target_url}}</p>
|
|
131
|
+
<p><strong>Number of Users:</strong> {{number_of_users}}</p>
|
|
132
|
+
<p><strong>Duration:</strong> {{duration}}</p>
|
|
133
|
+
<p><strong>Status:</strong> {{test_status}}</p>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<!-- Key Metrics Table -->
|
|
137
|
+
<h2>Key Metrics</h2>
|
|
138
|
+
<table>
|
|
139
|
+
<thead>
|
|
140
|
+
<tr>
|
|
141
|
+
<th>Metric</th>
|
|
142
|
+
<th>Value</th>
|
|
143
|
+
</tr>
|
|
144
|
+
</thead>
|
|
145
|
+
<tbody>
|
|
146
|
+
<tr>
|
|
147
|
+
<td>Average Response Time</td>
|
|
148
|
+
<td>{{average_response_time}} ms</td>
|
|
149
|
+
</tr>
|
|
150
|
+
<tr>
|
|
151
|
+
<td>Throughput</td>
|
|
152
|
+
<td>{{throughput}} requests/second</td>
|
|
153
|
+
</tr>
|
|
154
|
+
<tr>
|
|
155
|
+
<td>Error Rate</td>
|
|
156
|
+
<td>{{error_rate}} %</td>
|
|
157
|
+
</tr>
|
|
158
|
+
<tr>
|
|
159
|
+
<td>95th Percentile Response Time</td>
|
|
160
|
+
<td>{{percentile_95_response_time}} ms</td>
|
|
161
|
+
</tr>
|
|
162
|
+
</tbody>
|
|
163
|
+
</table>
|
|
164
|
+
|
|
165
|
+
<!-- Charts -->
|
|
166
|
+
<div class="charts">
|
|
167
|
+
<div class="chart">
|
|
168
|
+
<h3>Response Time Over Time</h3>
|
|
169
|
+
<img src="{{response_time_chart_url}}" alt="Response Time Chart">
|
|
170
|
+
</div>
|
|
171
|
+
<div class="chart">
|
|
172
|
+
<h3>Throughput Over Time</h3>
|
|
173
|
+
<img src="{{throughput_chart_url}}" alt="Throughput Chart">
|
|
174
|
+
</div>
|
|
175
|
+
<!-- Add more charts as needed -->
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<!-- Bottleneck Identification -->
|
|
179
|
+
<div class="bottlenecks">
|
|
180
|
+
<h3>Bottleneck Identification</h3>
|
|
181
|
+
<p>{{bottlenecks_summary}}</p>
|
|
182
|
+
<ul>
|
|
183
|
+
{{#bottlenecks}}
|
|
184
|
+
<li>{{.}}</li>
|
|
185
|
+
{{/bottlenecks}}
|
|
186
|
+
</ul>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<!-- Recommendations -->
|
|
190
|
+
<div class="recommendations">
|
|
191
|
+
<h3>Recommendations</h3>
|
|
192
|
+
<p>{{recommendations_summary}}</p>
|
|
193
|
+
<ul>
|
|
194
|
+
{{#recommendations}}
|
|
195
|
+
<li>{{.}}</li>
|
|
196
|
+
{{/recommendations}}
|
|
197
|
+
</ul>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
</body>
|
|
203
|
+
</html>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// test_template.js - Template for K6 performance tests
|
|
2
|
+
|
|
3
|
+
// Import necessary modules from K6
|
|
4
|
+
import http from 'k6/http';
|
|
5
|
+
import { check, sleep } from 'k6';
|
|
6
|
+
|
|
7
|
+
// Configuration options
|
|
8
|
+
export const options = {
|
|
9
|
+
// Stages define the load pattern
|
|
10
|
+
stages: [
|
|
11
|
+
// Example: Ramp-up to 10 virtual users (VUs) over 10 seconds
|
|
12
|
+
{ duration: '10s', target: 10 },
|
|
13
|
+
|
|
14
|
+
// Example: Maintain 10 VUs for 30 seconds
|
|
15
|
+
{ duration: '30s', target: 10 },
|
|
16
|
+
|
|
17
|
+
// Example: Ramp-down to 0 VUs over 10 seconds
|
|
18
|
+
{ duration: '10s', target: 0 },
|
|
19
|
+
],
|
|
20
|
+
|
|
21
|
+
// Thresholds define pass/fail criteria
|
|
22
|
+
thresholds: {
|
|
23
|
+
// Example: 95th percentile response time should be below 200ms
|
|
24
|
+
http_req_duration: ['p95<200'],
|
|
25
|
+
|
|
26
|
+
// Example: 99% of requests should be successful
|
|
27
|
+
http_req_failed: ['rate<0.01'], // <1% failure rate
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Define the virtual user (VU) function
|
|
32
|
+
export default function () {
|
|
33
|
+
// Replace with your target URL
|
|
34
|
+
const url = 'YOUR_TARGET_URL_HERE';
|
|
35
|
+
|
|
36
|
+
// Replace with your request parameters (optional)
|
|
37
|
+
const params = {
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Replace with your request body (optional)
|
|
44
|
+
const payload = JSON.stringify({
|
|
45
|
+
key1: 'value1',
|
|
46
|
+
key2: 'value2',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Make an HTTP request
|
|
50
|
+
const res = http.get(url, params); // or use http.post, http.put, http.delete, etc.
|
|
51
|
+
|
|
52
|
+
// Check the response status code
|
|
53
|
+
check(res, {
|
|
54
|
+
'status is 200': (r) => r.status === 200,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Add more checks as needed
|
|
58
|
+
// Example: check(res, { 'response time < 500ms': (r) => r.timings.duration < 500 });
|
|
59
|
+
|
|
60
|
+
// Introduce a delay between requests (optional)
|
|
61
|
+
sleep(1); // Sleep for 1 second
|
|
62
|
+
|
|
63
|
+
// Log response data for debugging (optional - REMOVE IN PRODUCTION!)
|
|
64
|
+
// console.log(res.body);
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Scripts
|
|
2
|
+
|
|
3
|
+
Bundled resources for performance-test-suite skill
|
|
4
|
+
|
|
5
|
+
- [x] init_test.py: Script to initialize a performance test based on user input.
|
|
6
|
+
- [x] run_test.sh: Script to execute the performance test using k6 or other tools.
|
|
7
|
+
- [x] analyze_results.py: Script to analyze the test results and generate a report.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Auto-Generated
|
|
11
|
+
Scripts generated on 2025-12-10 03:48:17
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
performance-test-suite - Analysis Script
|
|
4
|
+
Script to analyze the test results and generate a report.
|
|
5
|
+
Generated: 2025-12-10 03:48:17
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import argparse
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
class Analyzer:
|
|
16
|
+
def __init__(self, target_path: str):
|
|
17
|
+
self.target_path = Path(target_path)
|
|
18
|
+
self.stats = {
|
|
19
|
+
'total_files': 0,
|
|
20
|
+
'total_size': 0,
|
|
21
|
+
'file_types': {},
|
|
22
|
+
'issues': [],
|
|
23
|
+
'recommendations': []
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def analyze_directory(self) -> Dict:
|
|
27
|
+
"""Analyze directory structure and contents."""
|
|
28
|
+
if not self.target_path.exists():
|
|
29
|
+
self.stats['issues'].append(f"Path does not exist: {self.target_path}")
|
|
30
|
+
return self.stats
|
|
31
|
+
|
|
32
|
+
for file_path in self.target_path.rglob('*'):
|
|
33
|
+
if file_path.is_file():
|
|
34
|
+
self.analyze_file(file_path)
|
|
35
|
+
|
|
36
|
+
return self.stats
|
|
37
|
+
|
|
38
|
+
def analyze_file(self, file_path: Path):
|
|
39
|
+
"""Analyze individual file."""
|
|
40
|
+
self.stats['total_files'] += 1
|
|
41
|
+
self.stats['total_size'] += file_path.stat().st_size
|
|
42
|
+
|
|
43
|
+
# Track file types
|
|
44
|
+
ext = file_path.suffix.lower()
|
|
45
|
+
if ext:
|
|
46
|
+
self.stats['file_types'][ext] = self.stats['file_types'].get(ext, 0) + 1
|
|
47
|
+
|
|
48
|
+
# Check for potential issues
|
|
49
|
+
if file_path.stat().st_size > 100 * 1024 * 1024: # 100MB
|
|
50
|
+
self.stats['issues'].append(f"Large file: {file_path} ({file_path.stat().st_size // 1024 // 1024}MB)")
|
|
51
|
+
|
|
52
|
+
if file_path.stat().st_size == 0:
|
|
53
|
+
self.stats['issues'].append(f"Empty file: {file_path}")
|
|
54
|
+
|
|
55
|
+
def generate_recommendations(self):
|
|
56
|
+
"""Generate recommendations based on analysis."""
|
|
57
|
+
if self.stats['total_files'] == 0:
|
|
58
|
+
self.stats['recommendations'].append("No files found - check target path")
|
|
59
|
+
|
|
60
|
+
if len(self.stats['file_types']) > 20:
|
|
61
|
+
self.stats['recommendations'].append("Many file types detected - consider organizing")
|
|
62
|
+
|
|
63
|
+
if self.stats['total_size'] > 1024 * 1024 * 1024: # 1GB
|
|
64
|
+
self.stats['recommendations'].append("Large total size - consider archiving old data")
|
|
65
|
+
|
|
66
|
+
def generate_report(self) -> str:
|
|
67
|
+
"""Generate analysis report."""
|
|
68
|
+
report = []
|
|
69
|
+
report.append("\n" + "="*60)
|
|
70
|
+
report.append(f"ANALYSIS REPORT - performance-test-suite")
|
|
71
|
+
report.append("="*60)
|
|
72
|
+
report.append(f"Target: {self.target_path}")
|
|
73
|
+
report.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
74
|
+
report.append("")
|
|
75
|
+
|
|
76
|
+
# Statistics
|
|
77
|
+
report.append("š STATISTICS")
|
|
78
|
+
report.append(f" Total Files: {self.stats['total_files']:,}")
|
|
79
|
+
report.append(f" Total Size: {self.stats['total_size'] / 1024 / 1024:.2f} MB")
|
|
80
|
+
report.append(f" File Types: {len(self.stats['file_types'])}")
|
|
81
|
+
|
|
82
|
+
# Top file types
|
|
83
|
+
if self.stats['file_types']:
|
|
84
|
+
report.append("\nš TOP FILE TYPES")
|
|
85
|
+
sorted_types = sorted(self.stats['file_types'].items(), key=lambda x: x[1], reverse=True)[:5]
|
|
86
|
+
for ext, count in sorted_types:
|
|
87
|
+
report.append(f" {ext or 'no extension'}: {count} files")
|
|
88
|
+
|
|
89
|
+
# Issues
|
|
90
|
+
if self.stats['issues']:
|
|
91
|
+
report.append(f"\nā ļø ISSUES ({len(self.stats['issues'])})")
|
|
92
|
+
for issue in self.stats['issues'][:10]:
|
|
93
|
+
report.append(f" - {issue}")
|
|
94
|
+
if len(self.stats['issues']) > 10:
|
|
95
|
+
report.append(f" ... and {len(self.stats['issues']) - 10} more")
|
|
96
|
+
|
|
97
|
+
# Recommendations
|
|
98
|
+
if self.stats['recommendations']:
|
|
99
|
+
report.append("\nš” RECOMMENDATIONS")
|
|
100
|
+
for rec in self.stats['recommendations']:
|
|
101
|
+
report.append(f" - {rec}")
|
|
102
|
+
|
|
103
|
+
report.append("")
|
|
104
|
+
return "\n".join(report)
|
|
105
|
+
|
|
106
|
+
def main():
|
|
107
|
+
parser = argparse.ArgumentParser(description="Script to analyze the test results and generate a report.")
|
|
108
|
+
parser.add_argument('target', help='Target directory to analyze')
|
|
109
|
+
parser.add_argument('--output', '-o', help='Output report file')
|
|
110
|
+
parser.add_argument('--json', action='store_true', help='Output as JSON')
|
|
111
|
+
|
|
112
|
+
args = parser.parse_args()
|
|
113
|
+
|
|
114
|
+
print(f"š Analyzing {args.target}...")
|
|
115
|
+
analyzer = Analyzer(args.target)
|
|
116
|
+
stats = analyzer.analyze_directory()
|
|
117
|
+
analyzer.generate_recommendations()
|
|
118
|
+
|
|
119
|
+
if args.json:
|
|
120
|
+
output = json.dumps(stats, indent=2)
|
|
121
|
+
else:
|
|
122
|
+
output = analyzer.generate_report()
|
|
123
|
+
|
|
124
|
+
if args.output:
|
|
125
|
+
Path(args.output).write_text(output)
|
|
126
|
+
print(f"ā Report saved to {args.output}")
|
|
127
|
+
else:
|
|
128
|
+
print(output)
|
|
129
|
+
|
|
130
|
+
return 0 if len(stats['issues']) == 0 else 1
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
import sys
|
|
134
|
+
sys.exit(main())
|