overload-cli 0.1.0__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.
Files changed (40) hide show
  1. overload/__init__.py +3 -0
  2. overload/__main__.py +5 -0
  3. overload/cli.py +393 -0
  4. overload/collection/__init__.py +1 -0
  5. overload/collection/environment.py +23 -0
  6. overload/collection/models.py +88 -0
  7. overload/collection/parser.py +220 -0
  8. overload/collection/variables.py +84 -0
  9. overload/config_file.py +73 -0
  10. overload/engine/__init__.py +1 -0
  11. overload/engine/assertions.py +151 -0
  12. overload/engine/auth.py +87 -0
  13. overload/engine/events.py +50 -0
  14. overload/engine/http_client.py +274 -0
  15. overload/engine/load_patterns.py +730 -0
  16. overload/engine/models.py +254 -0
  17. overload/engine/rate_limiter.py +124 -0
  18. overload/engine/runner.py +86 -0
  19. overload/report/__init__.py +1 -0
  20. overload/report/exporters.py +77 -0
  21. overload/report/generator.py +71 -0
  22. overload/report/templates/report.html +369 -0
  23. overload/utils/__init__.py +1 -0
  24. overload/utils/naming.py +26 -0
  25. overload/web/__init__.py +1 -0
  26. overload/web/app.py +38 -0
  27. overload/web/routes/__init__.py +1 -0
  28. overload/web/routes/api.py +461 -0
  29. overload/web/routes/ws.py +77 -0
  30. overload/web/static/css/app.css +242 -0
  31. overload/web/static/js/app.js +241 -0
  32. overload/web/static/js/charts.js +385 -0
  33. overload/web/static/js/collection.js +344 -0
  34. overload/web/static/js/runner.js +625 -0
  35. overload/web/templates/index.html +23 -0
  36. overload_cli-0.1.0.dist-info/METADATA +267 -0
  37. overload_cli-0.1.0.dist-info/RECORD +40 -0
  38. overload_cli-0.1.0.dist-info/WHEEL +4 -0
  39. overload_cli-0.1.0.dist-info/entry_points.txt +2 -0
  40. overload_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,385 @@
1
+ window.OverloadCharts = (function() {
2
+ var chartInstances = {};
3
+
4
+ function destroyChart(id) {
5
+ if (chartInstances[id]) {
6
+ chartInstances[id].destroy();
7
+ delete chartInstances[id];
8
+ }
9
+ }
10
+
11
+ function createChart(id, config) {
12
+ var el = document.getElementById(id);
13
+ if (!el) return null;
14
+ destroyChart(id);
15
+ var chart = new Chart(el, config);
16
+ chartInstances[id] = chart;
17
+ return chart;
18
+ }
19
+
20
+ var colors = {
21
+ ok: 'rgba(14,138,95,0.7)',
22
+ okBg: 'rgba(14,138,95,0.1)',
23
+ bad: 'rgba(192,57,43,0.7)',
24
+ badBg: 'rgba(192,57,43,0.1)',
25
+ mid: 'rgba(183,96,10,0.7)',
26
+ blue: 'rgba(29,95,168,0.7)',
27
+ purple: 'rgba(124,58,237,0.7)',
28
+ grid: '#e5e7eb',
29
+ text: '#6b7280'
30
+ };
31
+
32
+ function statusColor(code) {
33
+ if (code <= 0) return '#9ca3af';
34
+ if (code < 200) return '#6366f1';
35
+ if (code < 300) return colors.ok;
36
+ if (code < 400) return colors.blue;
37
+ if (code < 500) return colors.mid;
38
+ return colors.bad;
39
+ }
40
+
41
+ function rpsBarChart(id, perSecond) {
42
+ if (!perSecond || !perSecond.length) return;
43
+
44
+ var data = perSecond;
45
+ var maxSec = perSecond[perSecond.length - 1].second;
46
+ if (maxSec > 120) {
47
+ var bucketSz = maxSec > 600 ? 10 : 5;
48
+ var agg = {};
49
+ perSecond.forEach(function(r) {
50
+ var k = Math.floor(r.second / bucketSz) * bucketSz;
51
+ if (!agg[k]) agg[k] = { second: k, ok: 0, rate_limited: 0, client_errors: 0, server_errors: 0, conn_errors: 0 };
52
+ agg[k].ok += r.ok;
53
+ agg[k].rate_limited += r.rate_limited;
54
+ agg[k].client_errors += (r.client_errors || 0);
55
+ agg[k].server_errors += (r.server_errors || 0);
56
+ agg[k].conn_errors += (r.conn_errors || 0);
57
+ });
58
+ data = Object.keys(agg).sort(function(a, b) { return a - b; }).map(function(k) { return agg[k]; });
59
+ }
60
+
61
+ createChart(id, {
62
+ type: 'bar',
63
+ data: {
64
+ labels: data.map(function(r) { return fmtTime(r.second); }),
65
+ datasets: [
66
+ { label: '2xx', data: data.map(function(r) { return r.ok; }), backgroundColor: colors.ok, borderRadius: 2 },
67
+ { label: '4xx', data: data.map(function(r) { return r.client_errors || 0; }), backgroundColor: colors.mid, borderRadius: 2 },
68
+ { label: '429', data: data.map(function(r) { return r.rate_limited; }), backgroundColor: colors.purple, borderRadius: 2 },
69
+ { label: '5xx', data: data.map(function(r) { return r.server_errors || 0; }), backgroundColor: colors.bad, borderRadius: 2 },
70
+ { label: 'Err', data: data.map(function(r) { return r.conn_errors || 0; }), backgroundColor: '#9ca3af', borderRadius: 2 }
71
+ ]
72
+ },
73
+ options: {
74
+ responsive: true, maintainAspectRatio: false,
75
+ plugins: { legend: { labels: { color: colors.text } } },
76
+ scales: {
77
+ x: { stacked: true, grid: { color: colors.grid }, ticks: { maxTicksLimit: 20 } },
78
+ y: { stacked: true, grid: { color: colors.grid } }
79
+ }
80
+ }
81
+ });
82
+ }
83
+
84
+ function statusDoughnut(id, statusCodes) {
85
+ if (!statusCodes || !Object.keys(statusCodes).length) return;
86
+ var labels = Object.keys(statusCodes).sort(function(a, b) { return Number(a) - Number(b); });
87
+ var values = labels.map(function(k) { return statusCodes[k]; });
88
+ var bgColors = labels.map(function(k) { return statusColor(Number(k)); });
89
+ createChart(id, {
90
+ type: 'doughnut',
91
+ data: { labels: labels, datasets: [{ data: values, backgroundColor: bgColors, borderWidth: 1, borderColor: '#fff', hoverOffset: 4 }] },
92
+ options: {
93
+ responsive: true, maintainAspectRatio: false, cutout: '65%',
94
+ plugins: { legend: { position: 'right', labels: { color: colors.text, padding: 10 } } }
95
+ }
96
+ });
97
+ }
98
+
99
+ var LATENCY_BUCKETS = [10, 25, 50, 100, 200, 300, 500, 750, 1000, 2000, 5000];
100
+
101
+ function latencyHistogram(id, timeline) {
102
+ if (!timeline || !timeline.length) return;
103
+ var lats = timeline.map(function(r) { return r.latency_ms; }).filter(function(v) { return v >= 0 && v < 60000; });
104
+ if (!lats.length) return;
105
+
106
+ var maxLat = Math.max.apply(null, lats);
107
+ var buckets = LATENCY_BUCKETS.filter(function(b) { return b <= maxLat * 1.2; });
108
+ if (!buckets.length) buckets = [10, 25, 50];
109
+ var labels = [];
110
+ var counts = [];
111
+ var prev = 0;
112
+ for (var i = 0; i < buckets.length; i++) {
113
+ var b = buckets[i];
114
+ var label = prev === 0 ? '<' + b + 'ms' : prev + '-' + b + 'ms';
115
+ var count = 0;
116
+ for (var j = 0; j < lats.length; j++) {
117
+ if (lats[j] >= prev && lats[j] < b) count++;
118
+ }
119
+ labels.push(label);
120
+ counts.push(count);
121
+ prev = b;
122
+ }
123
+ var overCount = 0;
124
+ for (var k = 0; k < lats.length; k++) {
125
+ if (lats[k] >= prev) overCount++;
126
+ }
127
+ if (overCount > 0) {
128
+ labels.push('>' + prev + 'ms');
129
+ counts.push(overCount);
130
+ }
131
+
132
+ var barColors = counts.map(function(_, idx) {
133
+ var ratio = idx / Math.max(labels.length - 1, 1);
134
+ if (ratio < 0.5) return colors.ok;
135
+ if (ratio < 0.75) return colors.mid;
136
+ return colors.bad;
137
+ });
138
+
139
+ createChart(id, {
140
+ type: 'bar',
141
+ data: { labels: labels, datasets: [{ label: 'Requests', data: counts, backgroundColor: barColors, borderRadius: 3 }] },
142
+ options: {
143
+ responsive: true, maintainAspectRatio: false,
144
+ plugins: {
145
+ legend: { display: false },
146
+ tooltip: {
147
+ callbacks: {
148
+ label: function(ctx) {
149
+ var total = lats.length;
150
+ var pct = total > 0 ? (ctx.raw * 100 / total).toFixed(1) : 0;
151
+ return ctx.raw + ' requests (' + pct + '%)';
152
+ }
153
+ }
154
+ }
155
+ },
156
+ scales: {
157
+ x: { grid: { display: false }, ticks: { maxRotation: 45, font: { size: 10 } } },
158
+ y: { grid: { color: colors.grid }, title: { display: true, text: 'Count', color: colors.text } }
159
+ }
160
+ }
161
+ });
162
+ }
163
+
164
+ function fmtTime(sec) {
165
+ if (sec < 60) return Math.round(sec * 10) / 10 + 's';
166
+ if (sec < 3600) return Math.round(sec / 6) / 10 + 'm';
167
+ return Math.round(sec / 360) / 10 + 'h';
168
+ }
169
+
170
+ function fmtLat(ms) {
171
+ if (ms < 1000) return Math.round(ms) + 'ms';
172
+ return (Math.round(ms / 100) / 10) + 's';
173
+ }
174
+
175
+ function timelineScatter(id, timeline) {
176
+ if (!timeline || !timeline.length) return;
177
+ var n = timeline.length;
178
+ var maxT = Math.max(timeline[n - 1].timestamp, 0.1);
179
+
180
+ if (n <= 50) {
181
+ var pts = timeline.map(function(r) { return { x: r.timestamp, y: r.latency_ms }; });
182
+ createChart(id, {
183
+ type: 'line',
184
+ data: {
185
+ labels: timeline.map(function(r) { return fmtTime(r.timestamp); }),
186
+ datasets: [{
187
+ label: 'Latency',
188
+ data: timeline.map(function(r) { return Math.round(r.latency_ms * 10) / 10; }),
189
+ borderColor: colors.ok, backgroundColor: colors.okBg,
190
+ fill: true, tension: 0.3, pointRadius: n < 20 ? 3 : 1, borderWidth: 2,
191
+ pointBackgroundColor: timeline.map(function(r) {
192
+ if (r.status <= 0) return '#9ca3af';
193
+ if (r.status < 300) return colors.ok;
194
+ if (r.status < 400) return colors.blue;
195
+ if (r.status === 429) return colors.purple;
196
+ if (r.status < 500) return colors.mid;
197
+ return colors.bad;
198
+ })
199
+ }]
200
+ },
201
+ options: {
202
+ responsive: true, maintainAspectRatio: false,
203
+ plugins: {
204
+ legend: { display: false },
205
+ tooltip: { callbacks: { label: function(ctx) { var r = timeline[ctx.dataIndex]; return r.status + ' — ' + fmtLat(r.latency_ms); } } }
206
+ },
207
+ scales: {
208
+ x: { title: { display: true, text: 'Time', color: colors.text }, grid: { color: colors.grid } },
209
+ y: { title: { display: true, text: 'Latency', color: colors.text }, grid: { color: colors.grid }, beginAtZero: true,
210
+ ticks: { callback: function(v) { return fmtLat(v); } } }
211
+ }
212
+ }
213
+ });
214
+ return;
215
+ }
216
+
217
+ var bucketCount = Math.min(80, Math.max(15, Math.ceil(maxT)));
218
+ var bucketSize = maxT / bucketCount || 1;
219
+ var bkts = [];
220
+ for (var b = 0; b < bucketCount; b++) bkts.push({ lats: [] });
221
+ timeline.forEach(function(r) {
222
+ var bi = Math.min(Math.floor(r.timestamp / bucketSize), bucketCount - 1);
223
+ bkts[bi].lats.push(r.latency_ms);
224
+ });
225
+
226
+ var labels = [], avgData = [], p50Data = [], p95Data = [];
227
+ for (var i = 0; i < bucketCount; i++) {
228
+ labels.push(fmtTime((i + 0.5) * bucketSize));
229
+ var sl = bkts[i].lats.slice().sort(function(a, b) { return a - b; });
230
+ if (!sl.length) { avgData.push(null); p50Data.push(null); p95Data.push(null); continue; }
231
+ var sum = 0; sl.forEach(function(v) { sum += v; });
232
+ avgData.push(Math.round(sum / sl.length));
233
+ p50Data.push(Math.round(sl[Math.floor(sl.length * 0.5)]));
234
+ p95Data.push(Math.round(sl[Math.min(Math.floor(sl.length * 0.95), sl.length - 1)]));
235
+ }
236
+
237
+ createChart(id, {
238
+ type: 'line',
239
+ data: {
240
+ labels: labels,
241
+ datasets: [
242
+ { label: 'Avg', data: avgData, borderColor: colors.ok, backgroundColor: colors.okBg, fill: true, tension: 0.3, pointRadius: 0, borderWidth: 2 },
243
+ { label: 'P50', data: p50Data, borderColor: colors.blue, tension: 0.3, pointRadius: 0, borderWidth: 1.5, borderDash: [4, 2] },
244
+ { label: 'P95', data: p95Data, borderColor: colors.bad, tension: 0.3, pointRadius: 0, borderWidth: 1.5, borderDash: [4, 2] }
245
+ ]
246
+ },
247
+ options: {
248
+ responsive: true, maintainAspectRatio: false, spanGaps: true,
249
+ plugins: { legend: { labels: { color: colors.text } } },
250
+ scales: {
251
+ x: { title: { display: true, text: 'Time', color: colors.text }, grid: { color: colors.grid }, ticks: { maxTicksLimit: 12 } },
252
+ y: { title: { display: true, text: 'Latency', color: colors.text }, grid: { color: colors.grid }, beginAtZero: true,
253
+ ticks: { callback: function(v) { return fmtLat(v); } } }
254
+ }
255
+ }
256
+ });
257
+ }
258
+
259
+ function loadShapePreview(id, testType, config) {
260
+ var points = generateShapePoints(testType, config);
261
+ if (!points.length) return;
262
+ createChart(id, {
263
+ type: 'line',
264
+ data: {
265
+ labels: points.map(function(p) { return p.t + 's'; }),
266
+ datasets: [{
267
+ label: 'RPS',
268
+ data: points.map(function(p) { return p.rps; }),
269
+ borderColor: colors.ok,
270
+ backgroundColor: colors.okBg,
271
+ fill: true, tension: 0.2, pointRadius: 0
272
+ }]
273
+ },
274
+ options: {
275
+ responsive: true, maintainAspectRatio: false,
276
+ plugins: { legend: { display: false } },
277
+ scales: {
278
+ x: { grid: { color: colors.grid }, ticks: { maxTicksLimit: 8 } },
279
+ y: { grid: { color: colors.grid }, beginAtZero: true, title: { display: true, text: 'req/s', color: colors.text } }
280
+ }
281
+ }
282
+ });
283
+ }
284
+
285
+ function liveRpsLine(id, rpsData) {
286
+ if (!rpsData || !rpsData.length) return;
287
+
288
+ var existing = chartInstances[id];
289
+ if (existing && existing.data) {
290
+ existing.data.labels = rpsData.map(function(p) { return p.t + 's'; });
291
+ existing.data.datasets[0].data = rpsData.map(function(p) { return p.rps; });
292
+ existing.update('none');
293
+ return;
294
+ }
295
+
296
+ createChart(id, {
297
+ type: 'line',
298
+ data: {
299
+ labels: rpsData.map(function(p) { return p.t + 's'; }),
300
+ datasets: [{
301
+ label: 'RPS',
302
+ data: rpsData.map(function(p) { return p.rps; }),
303
+ borderColor: colors.ok,
304
+ backgroundColor: colors.okBg,
305
+ fill: true, tension: 0.3, pointRadius: 0, borderWidth: 2
306
+ }]
307
+ },
308
+ options: {
309
+ responsive: true, maintainAspectRatio: false,
310
+ animation: false,
311
+ plugins: { legend: { display: false } },
312
+ scales: {
313
+ x: { grid: { color: colors.grid }, ticks: { maxTicksLimit: 10, font: { size: 9 } } },
314
+ y: { grid: { color: colors.grid }, beginAtZero: true, title: { display: true, text: 'req/s', color: colors.text } }
315
+ }
316
+ }
317
+ });
318
+ }
319
+
320
+ function generateShapePoints(testType, c) {
321
+ var points = [];
322
+ var t = 0;
323
+ switch (testType) {
324
+ case 'load':
325
+ var rampUp = c.ramp_up_seconds || 30;
326
+ var hold = c.hold_duration_seconds || 300;
327
+ var rampDown = c.ramp_down_seconds || 10;
328
+ var target = c.target_rps || 50;
329
+ for (var i = 0; i <= rampUp; i += Math.max(1, Math.floor(rampUp / 10))) { points.push({ t: t + i, rps: Math.round(target * i / rampUp) }); }
330
+ t += rampUp;
331
+ points.push({ t: t, rps: target });
332
+ points.push({ t: t + hold, rps: target });
333
+ t += hold;
334
+ for (var j = 0; j <= rampDown; j += Math.max(1, Math.floor(rampDown / 5))) { points.push({ t: t + j, rps: Math.round(target * (rampDown - j) / rampDown) }); }
335
+ break;
336
+ case 'stress':
337
+ var start = c.start_rps || 10, step = c.step_rps || 20, max = c.max_rps || 500, dur = c.step_duration_seconds || 30;
338
+ for (var rps = start; rps <= max; rps += step) { points.push({ t: t, rps: rps }); points.push({ t: t + dur, rps: rps }); t += dur; }
339
+ break;
340
+ case 'spike':
341
+ var base = c.baseline_rps || 20, spike = c.spike_rps || 200;
342
+ var bDur = c.baseline_duration_seconds || 60, sDur = c.spike_duration_seconds || 30, rDur = c.recovery_duration_seconds || 60;
343
+ points.push({ t: 0, rps: base }, { t: bDur, rps: base }, { t: bDur + 1, rps: spike }, { t: bDur + sDur, rps: spike }, { t: bDur + sDur + 1, rps: base }, { t: bDur + sDur + rDur, rps: base });
344
+ break;
345
+ case 'soak':
346
+ var soakRps = c.soak_rps || 30, soakDur = c.soak_duration_seconds || 1800;
347
+ points.push({ t: 0, rps: soakRps }, { t: soakDur, rps: soakRps });
348
+ break;
349
+ case 'ramp':
350
+ var rStart = c.ramp_start_rps || 10, rEnd = c.ramp_end_rps || 200, rStep = c.step_rps || 10, rDurS = c.step_duration_seconds || 15;
351
+ for (var r = rStart; r <= rEnd; r += rStep) { points.push({ t: t, rps: r }); points.push({ t: t + rDurS, rps: r }); t += rDurS; }
352
+ break;
353
+ case 'burst':
354
+ var n = c.total_requests || 200;
355
+ points.push({ t: 0, rps: n }, { t: 1, rps: n }, { t: 2, rps: 0 });
356
+ break;
357
+ case 'breakpoint':
358
+ var bpStart = c.start_rps || 10, bpMax = c.max_rps || 500;
359
+ points.push({ t: 0, rps: bpStart }, { t: 60, rps: (bpStart + bpMax) / 2 }, { t: 120, rps: bpMax });
360
+ break;
361
+ case 'custom':
362
+ var stages = c.stages || [];
363
+ stages.forEach(function(s) { points.push({ t: t, rps: s.rps }); t += s.duration; points.push({ t: t, rps: s.rps }); });
364
+ break;
365
+ }
366
+ return points;
367
+ }
368
+
369
+ function destroyAll() {
370
+ Object.keys(chartInstances).forEach(destroyChart);
371
+ }
372
+
373
+ return {
374
+ rpsBarChart: rpsBarChart,
375
+ statusDoughnut: statusDoughnut,
376
+ latencyHistogram: latencyHistogram,
377
+ timelineScatter: timelineScatter,
378
+ loadShapePreview: loadShapePreview,
379
+ liveRpsLine: liveRpsLine,
380
+ destroyAll: destroyAll,
381
+ destroyChart: destroyChart,
382
+ colors: colors,
383
+ statusColor: statusColor
384
+ };
385
+ })();