@cloudbeat/atlas-cli 0.3.0 → 0.4.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.
Files changed (41) hide show
  1. package/README.md +46 -19
  2. package/dist/api.js +20 -0
  3. package/dist/cli.js +34 -6
  4. package/dist/commands/analyze.js +118 -0
  5. package/dist/commands/capacity.js +99 -0
  6. package/dist/commands/docs.js +57 -0
  7. package/dist/commands/files.js +18 -4
  8. package/dist/commands/plan.js +100 -0
  9. package/dist/commands/record.js +11 -1
  10. package/dist/commands/results.js +13 -7
  11. package/dist/commands/schema.js +86 -0
  12. package/dist/commands/skills.js +111 -0
  13. package/dist/commands/tests.js +73 -0
  14. package/dist/docs/agent-setup.md +109 -0
  15. package/dist/docs/agent-tasks.md +169 -0
  16. package/dist/docs/ai-agents.md +90 -0
  17. package/dist/docs/auth.md +74 -0
  18. package/dist/docs/automation.md +150 -0
  19. package/dist/docs/ci.md +105 -0
  20. package/dist/docs/clusters.md +48 -0
  21. package/dist/docs/commands.md +155 -0
  22. package/dist/docs/files.md +86 -0
  23. package/dist/docs/generators.md +52 -0
  24. package/dist/docs/getting-started.md +85 -0
  25. package/dist/docs/index.json +110 -0
  26. package/dist/docs/projects.md +50 -0
  27. package/dist/docs/recorder.md +143 -0
  28. package/dist/docs/results.md +79 -0
  29. package/dist/docs/running.md +80 -0
  30. package/dist/docs/skills.md +88 -0
  31. package/dist/docs/test-plans.md +92 -0
  32. package/dist/docs/troubleshooting.md +79 -0
  33. package/dist/plan.js +250 -0
  34. package/dist/skills/AGENTS.md +70 -0
  35. package/dist/skills/atlas-analyze/SKILL.md +104 -0
  36. package/dist/skills/atlas-correlate/SKILL.md +89 -0
  37. package/dist/skills/atlas-load-test/SKILL.md +154 -0
  38. package/dist/skills/atlas-record/SKILL.md +167 -0
  39. package/dist/skills/atlas-run/SKILL.md +109 -0
  40. package/dist/skills/atlas-test-plan/SKILL.md +281 -0
  41. package/package.json +5 -3
@@ -0,0 +1,88 @@
1
+ # The Skills
2
+
3
+ `atlas skills install` writes six skills plus a vendor-neutral `AGENTS.md`. Each
4
+ skill is a short document covering one part of load testing: what to do, in what
5
+ order, and which mistakes to avoid.
6
+
7
+ They are plain Markdown. Read them, edit them for your project, commit them —
8
+ they are yours once installed.
9
+
10
+ ## How they load
11
+
12
+ Claude Code reads each skill's description and loads the one that fits your
13
+ request, so you never name a skill yourself. Ask *"why did the test fail?"* and
14
+ it loads the results skill; ask *"record a checkout"* and it loads the recording
15
+ one. Several can load in one conversation as the work moves on.
16
+
17
+ Assistants without skill support read the same guidance, condensed, from
18
+ `AGENTS.md`.
19
+
20
+ ## What each one covers
21
+
22
+ ### `atlas-load-test`
23
+
24
+ The end-to-end flow, and the one that usually loads first for *"load test our
25
+ checkout"*. Picking or creating a plan, uploading data, sizing, the mandatory
26
+ smoke run, then the real run and the report. Mostly it sequences the others.
27
+
28
+ ### `atlas-record`
29
+
30
+ Recording a scenario from a real browser. Choosing who clicks — you or the
31
+ assistant — driving the browser through the accessibility tree, naming
32
+ transactions before performing them, signing in without handing over a password,
33
+ and the traps: native dialogs that freeze a session, one click recording two
34
+ requests, everything before the first action landing in one transaction.
35
+
36
+ ### `atlas-test-plan`
37
+
38
+ Plans and data files, and the longest of the six. Downloading before editing,
39
+ the three kinds of change in order of preference, and how these plans are laid
40
+ out — think-time settings, host settings, the numbered transactions that make a
41
+ report readable, and sizing through run-time properties.
42
+
43
+ It also carries the two things that save the most time in practice: the stages
44
+ for turning a fresh recording into a real test (flow → test data → dynamic
45
+ values → checks → upload), and a local debugging recipe that switches think time
46
+ off in a throwaway copy, runs one user, and restores the original — seconds
47
+ instead of minutes per attempt.
48
+
49
+ ### `atlas-correlate`
50
+
51
+ Values the server issues per session — CSRF tokens, session ids, cart ids — and
52
+ why a replayed one produces a fast, clean, meaningless test. Reviewing what the
53
+ recorder detected with its evidence, pinning what it missed, and telling those
54
+ apart from values that belong in a data file instead.
55
+
56
+ ### `atlas-run`
57
+
58
+ Starting and sizing a run: how users map onto machines, ramp-up, fixed machines
59
+ versus a cluster, pass/fail thresholds, exit codes and CI usage, and what to do
60
+ while a run is queued behind someone else's.
61
+
62
+ ### `atlas-analyze`
63
+
64
+ Reading a result: the aggregation report, the shape of a run over time, the
65
+ errors behind a failure — and recognising the failure modes where a broken test
66
+ looks like a healthy one. If you only read one, read this one.
67
+
68
+ ## Editing them
69
+
70
+ Installed skills are ordinary files in your repository. Adding your own
71
+ conventions is normal and encouraged:
72
+
73
+ - which project maps to which service
74
+ - the thresholds your team gates on
75
+ - environments that must never be load tested
76
+ - who to ask before a run above a certain size
77
+
78
+ `atlas skills install` will not overwrite a file you have edited unless you pass
79
+ `--force`, so your changes survive an upgrade. When you do upgrade, diff first.
80
+
81
+ ## Listing what you have
82
+
83
+ ```sh
84
+ atlas skills list # what this CLI version carries
85
+ atlas skills install --force # refresh after upgrading the CLI
86
+ ```
87
+
88
+ [Build your own integration →](/agents/automation)
@@ -0,0 +1,92 @@
1
+ # Test Plan Requirements
2
+
3
+ Atlas runs standard JMeter test plans, but a plan written on a desktop needs a
4
+ few changes before it runs correctly on a fleet of Linux load generators. These
5
+ are the things that actually catch people out.
6
+
7
+ ## Data file paths
8
+
9
+ Reference data files by a plain relative path:
10
+
11
+ ```
12
+ ./users.csv ✅
13
+ users.csv ✅
14
+ C:\Users\me\users.csv ❌
15
+ /home/me/data/users.csv ❌
16
+ ./data/users.csv ❌
17
+ ```
18
+
19
+ A project's files live in one flat folder — see [Test Files](/guide/test-files).
20
+
21
+ If a **CSV Data Set Config** path can't be resolved, every thread in that thread
22
+ group throws before issuing a single request. There are no failed samples: the
23
+ summary reports `0.00%` errors and the dashboard **omits that thread group**
24
+ entirely while the others look healthy.
25
+
26
+ If a thread group is missing from your results, check its data file paths first.
27
+
28
+ Every file the plan references must also actually be uploaded.
29
+
30
+ ## Disable GUI listeners
31
+
32
+ Switch these off before uploading:
33
+
34
+ - **View Results Tree**
35
+ - **Aggregate Report**
36
+ - **Simple Data Writer**
37
+
38
+ They consume a lot of memory during a headless run — enough to destabilise a
39
+ long test — and Atlas collects its own results regardless. A **Simple Data
40
+ Writer** pointing at a `C:\...` path is doubly useless.
41
+
42
+ ## Thread counts are per machine
43
+
44
+ Atlas does not divide your threads across machines. A 100-thread plan on 5
45
+ generators is **500** concurrent users.
46
+
47
+ Decide the number you want in total, then divide by how many generators you plan
48
+ to use.
49
+
50
+ ## Header rows in CSV files
51
+
52
+ Whether line 1 of a data file is a header is decided by your plan's **CSV Data
53
+ Set Config**, not guessed:
54
+
55
+ - If **Variable Names** is filled in and **Ignore first line** is true, line 1 is
56
+ a header.
57
+ - If **Variable Names** is empty, JMeter reads line 1 as the names — so it counts
58
+ as a header too.
59
+
60
+ Get this right and each machine's slice keeps the header while the data rows are
61
+ divided. Get it wrong and you either inject a bogus row or lose a real one.
62
+
63
+ ## Timers and pacing
64
+
65
+ Think time matters more in a distributed test, not less. Without it, every
66
+ virtual user hammers as fast as the network allows, which produces a number that
67
+ looks impressive and means nothing.
68
+
69
+ ## Parameterising the target
70
+
71
+ If you test more than one environment, drive the host from a variable rather
72
+ than hardcoding it, and pass it at run time:
73
+
74
+ ```sh
75
+ atlas run -p SHOP --args "-Jhost=staging.example.com"
76
+ ```
77
+
78
+ ## A checklist
79
+
80
+ Before your first run:
81
+
82
+ - [ ] Data file paths are relative (`./file.csv`)
83
+ - [ ] Every referenced file is uploaded
84
+ - [ ] GUI listeners disabled
85
+ - [ ] Thread count is per-machine, and you've multiplied by your generator count
86
+ - [ ] Header handling matches your files
87
+ - [ ] Timers present
88
+ - [ ] The plan runs locally in headless mode first: `jmeter -n -t plan.jmx`
89
+
90
+ That last one is the highest-value check. `jmeter -n -t plan.jmx` on your own
91
+ machine catches most problems in seconds, without waiting for machines to power
92
+ on.
@@ -0,0 +1,79 @@
1
+ # Troubleshooting
2
+
3
+ ## A thread group is missing from my results
4
+
5
+ The usual cause is a **data file path that can't be resolved**. Every thread in
6
+ that group fails before sending a request, so there are no errors to report — the
7
+ group simply isn't there.
8
+
9
+ Check the **CSV Data Set Config** entries in that thread group and make each one
10
+ a relative path (`./data.csv`), then confirm the file is uploaded. See
11
+ [Test Plan Requirements](/reference/test-plans).
12
+
13
+ ## The test finished in seconds with no results
14
+
15
+ Usually the plan didn't start properly. Check:
16
+
17
+ - The plan runs locally: `jmeter -n -t plan.jmx`
18
+ - Every referenced file is uploaded
19
+ - The machine is big enough — an undersized generator can fail to start JMeter
20
+ at all rather than running slowly
21
+
22
+ ## Max Users is lower than I expected
23
+
24
+ Max Users adds up the concurrent users reported by every machine. If you ran on
25
+ 5 generators and it shows roughly one machine's worth, the others weren't
26
+ producing load.
27
+
28
+ Check the Fleet page: were all the machines connected? Open **Watch** during a
29
+ run to see per-machine activity live.
30
+
31
+ ## A machine shows as on, but not connected
32
+
33
+ The machine is running but its agent isn't reaching Atlas. On the machine:
34
+
35
+ - Confirm the Atlas agent service is running
36
+ - Confirm it can reach your Atlas instance over outbound HTTPS
37
+ - Check for a proxy or firewall rule blocking outbound connections
38
+
39
+ The agent reconnects on its own once the connection is restored.
40
+
41
+ ## My test is queued and hasn't started
42
+
43
+ A load generator runs one test at a time. If the machines you asked for are
44
+ busy, your run waits and starts automatically when they free up. Either wait, or
45
+ choose different machines.
46
+
47
+ ## Stop said it couldn't reach a machine
48
+
49
+ Atlas only marks a machine stopped once it has confirmed the process actually
50
+ ended. If it can't reach one, it tells you and the run stays open — deliberately,
51
+ so a test can't keep running unnoticed while the record says it stopped.
52
+
53
+ Retry the stop when the machine is reachable again.
54
+
55
+ ## Error rate is high but I can't see why
56
+
57
+ Click the **Error %** cell for a request in the aggregation report. That opens
58
+ the actual errors — response codes, messages, and the full request and response
59
+ for each distinct failure.
60
+
61
+ If errors are concentrated in one endpoint, click its **Label** to chart that
62
+ request on its own and see when it started failing.
63
+
64
+ ## Results look wrong for one request
65
+
66
+ Check whether the same sampler name exists in more than one thread group. Atlas
67
+ keeps them as separate rows attributed to the right group — if you expected them
68
+ combined, that's why the numbers differ from a single-machine JMeter run.
69
+
70
+ ## A recorded plan fails on the second run
71
+
72
+ Usually a dynamic value that wasn't correlated — a token that was valid when you
73
+ recorded and isn't now. Re-record with correlation enabled (the default), and
74
+ check that the step which issues the token is part of the recording.
75
+
76
+ ## Getting help
77
+
78
+ When reporting a problem, include the **test ID** (`SHOP-20260818-194201`). It
79
+ identifies the run and everything recorded about it.
package/dist/plan.js ADDED
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ /**
3
+ * Reading a JMeter test plan well enough to say whether it will work here.
4
+ *
5
+ * This is an advisory scan, not the deploy path: the control plane does its own
6
+ * work on the copy it sends out (flattening data-file paths it recognises,
7
+ * switching off GUI listeners, attaching Atlas reporting). What it CANNOT fix is
8
+ * a data file that was never uploaded, a plan with no enabled thread group, or a
9
+ * file whose structure it cannot attach reporting to — and every one of those
10
+ * fails quietly at run time, which is what this exists to catch beforehand.
11
+ *
12
+ * Regex over the XML, deliberately: the same shape the control plane uses, no
13
+ * parser dependency, and it never rewrites anything.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.checkPlan = checkPlan;
17
+ /** Lower-cased basename, tolerant of Windows separators — matches how the
18
+ * control plane decides whether a reference means an uploaded file. */
19
+ function basenameKey(filename) {
20
+ const parts = filename.replace(/\\/g, '/').split('/');
21
+ return (parts[parts.length - 1] ?? filename).trim().toLowerCase();
22
+ }
23
+ function basename(filename) {
24
+ const parts = filename.replace(/\\/g, '/').split('/');
25
+ return (parts[parts.length - 1] ?? filename).trim();
26
+ }
27
+ function stringProp(block, name) {
28
+ const m = block.match(new RegExp(`<stringProp name="${name}">([\\s\\S]*?)</stringProp>`));
29
+ return m ? m[1].trim() : '';
30
+ }
31
+ function intProp(block, name) {
32
+ const m = block.match(new RegExp(`<(?:string|int)Prop name="${name}">([\\s\\S]*?)</(?:string|int)Prop>`));
33
+ return m ? m[1].trim() : '';
34
+ }
35
+ function attr(tag, name) {
36
+ return new RegExp(`${name}="([^"]*)"`).exec(tag)?.[1] ?? '';
37
+ }
38
+ /** JMeter treats a missing `enabled` attribute as true. */
39
+ function isEnabled(tag) {
40
+ return attr(tag, 'enabled') !== 'false';
41
+ }
42
+ const THREAD_GROUP_TAGS = [
43
+ 'ThreadGroup',
44
+ 'SetupThreadGroup',
45
+ 'PostThreadGroup',
46
+ 'com\\.blazemeter\\.jmeter\\.threads\\.concurrency\\.ConcurrencyThreadGroup',
47
+ 'kg\\.apc\\.jmeter\\.threads\\.UltimateThreadGroup',
48
+ ];
49
+ function readThreadGroups(jmx) {
50
+ const out = [];
51
+ for (const tag of THREAD_GROUP_TAGS) {
52
+ const re = new RegExp(`<(${tag})\\b([^>]*)>([\\s\\S]*?)</\\1>`, 'g');
53
+ let m;
54
+ while ((m = re.exec(jmx)) !== null) {
55
+ const [, kind, attrs, body] = m;
56
+ const threads = intProp(body, 'ThreadGroup.num_threads') || intProp(body, 'TargetLevel');
57
+ const property = /\$\{__P\(([^,)]+)/.exec(threads)?.[1]?.trim();
58
+ out.push({
59
+ name: attr(`<x ${attrs}>`, 'testname'),
60
+ kind,
61
+ enabled: isEnabled(`<x ${attrs}>`),
62
+ threads,
63
+ loops: intProp(body, 'LoopController.loops'),
64
+ ...(property ? { sizeProperty: property } : {}),
65
+ });
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+ function readDataFiles(jmx, uploaded) {
71
+ const known = uploaded ? new Set(uploaded.map(basenameKey)) : undefined;
72
+ // Keyed by the file it resolves to: a plan commonly reads one file from
73
+ // several data sets, and reporting it four times helps nobody.
74
+ const byFile = new Map();
75
+ const re = /<CSVDataSet\b([^>]*)>([\s\S]*?)<\/CSVDataSet>/g;
76
+ let m;
77
+ while ((m = re.exec(jmx)) !== null) {
78
+ const [, attrs, body] = m;
79
+ const reference = stringProp(body, 'filename');
80
+ if (!reference)
81
+ continue;
82
+ // A path built at run time can't be judged now, and the control plane leaves
83
+ // it alone for the same reason.
84
+ if (reference.includes('${'))
85
+ continue;
86
+ const key = basenameKey(reference);
87
+ const enabled = isEnabled(`<x ${attrs}>`);
88
+ const hasPath = /[\\/]/.test(reference);
89
+ const existing = byFile.get(key);
90
+ if (existing) {
91
+ if (!existing.references.includes(reference))
92
+ existing.references.push(reference);
93
+ existing.enabled ||= enabled;
94
+ existing.hasPath ||= hasPath;
95
+ continue;
96
+ }
97
+ byFile.set(key, {
98
+ filename: basename(reference),
99
+ references: [reference],
100
+ enabled,
101
+ hasPath,
102
+ ...(known ? { uploaded: known.has(key) } : {}),
103
+ });
104
+ }
105
+ return [...byFile.values()];
106
+ }
107
+ /** Listeners the control plane switches off in the deployed copy. */
108
+ const DISABLED_AT_DEPLOY = {
109
+ ViewResultsFullVisualizer: 'View Results Tree',
110
+ SimpleDataWriter: 'Simple Data Writer',
111
+ };
112
+ function readListeners(jmx) {
113
+ const out = [];
114
+ const re = /<ResultCollector\b([^>]*)>/g;
115
+ let m;
116
+ while ((m = re.exec(jmx)) !== null) {
117
+ const tag = m[0];
118
+ const guiclass = attr(tag, 'guiclass');
119
+ if (!(guiclass in DISABLED_AT_DEPLOY))
120
+ continue;
121
+ out.push({
122
+ guiclass,
123
+ name: attr(tag, 'testname') || DISABLED_AT_DEPLOY[guiclass],
124
+ enabled: isEnabled(tag),
125
+ });
126
+ }
127
+ return out;
128
+ }
129
+ /**
130
+ * Whether Atlas can attach its reporting.
131
+ *
132
+ * It is inserted as the last child of the Test Plan's hashTree, which needs the
133
+ * file to end in the standard way. A plan that fails this runs and produces
134
+ * nothing — no dashboard, no results — so it is an error, not a nicety.
135
+ */
136
+ function reportingCanAttach(jmx) {
137
+ return /<\/hashTree>(\s*)<\/hashTree>(\s*)<\/jmeterTestPlan>(\s*)$/.test(jmx);
138
+ }
139
+ function checkPlan(jmx, uploaded) {
140
+ const issues = [];
141
+ const threadGroups = readThreadGroups(jmx);
142
+ const dataFiles = readDataFiles(jmx, uploaded);
143
+ const listeners = readListeners(jmx);
144
+ const canAttach = reportingCanAttach(jmx);
145
+ if (!/<jmeterTestPlan\b/.test(jmx)) {
146
+ issues.push({
147
+ severity: 'error',
148
+ code: 'not-a-test-plan',
149
+ message: 'This file is not a JMeter test plan.',
150
+ });
151
+ }
152
+ const opens = (jmx.match(/<hashTree>/g) ?? []).length;
153
+ const closes = (jmx.match(/<\/hashTree>/g) ?? []).length;
154
+ if (opens !== closes) {
155
+ issues.push({
156
+ severity: 'error',
157
+ code: 'unbalanced',
158
+ message: `The plan's structure is broken: ${opens} <hashTree> against ${closes} closing tags.`,
159
+ fix: 'Restore the file and redo the edit — JMeter will not open it in this state.',
160
+ });
161
+ }
162
+ if (!canAttach) {
163
+ issues.push({
164
+ severity: 'error',
165
+ code: 'reporting-cannot-attach',
166
+ message: 'Atlas cannot attach its reporting to this plan, so a run would produce no results.',
167
+ fix: 'Open and re-save the plan in JMeter so it ends with the standard structure.',
168
+ });
169
+ }
170
+ const enabledGroups = threadGroups.filter(g => g.enabled);
171
+ if (threadGroups.length === 0) {
172
+ issues.push({
173
+ severity: 'error',
174
+ code: 'no-thread-groups',
175
+ message: 'The plan has no thread groups, so it would run nothing.',
176
+ });
177
+ }
178
+ else if (enabledGroups.length === 0) {
179
+ issues.push({
180
+ severity: 'error',
181
+ code: 'all-thread-groups-disabled',
182
+ message: `All ${threadGroups.length} thread group(s) are disabled, so the run would do nothing.`,
183
+ fix: 'Enable the one you mean to run.',
184
+ });
185
+ }
186
+ for (const file of dataFiles.filter(f => f.enabled)) {
187
+ const shown = file.references[0];
188
+ if (file.uploaded === false) {
189
+ // The one that hurts: every thread in the group fails before sampling, so
190
+ // the group disappears from the results with no errors to explain it.
191
+ issues.push({
192
+ severity: 'error',
193
+ code: 'data-file-missing',
194
+ message: `"${file.filename}" is read by the plan but is not in the project.`,
195
+ fix: `Upload it: atlas files upload ${file.filename}`,
196
+ });
197
+ }
198
+ else if (file.uploaded && file.hasPath) {
199
+ issues.push({
200
+ severity: 'info',
201
+ code: 'data-file-path',
202
+ message: `"${shown}" is a path; Atlas will use the uploaded "${file.filename}".`,
203
+ });
204
+ }
205
+ else if (file.uploaded === undefined && file.hasPath) {
206
+ issues.push({
207
+ severity: 'warning',
208
+ code: 'data-file-path-unchecked',
209
+ message: `"${shown}" is a path from another machine.`,
210
+ fix: `Make sure "${file.filename}" is uploaded to the project; then Atlas resolves it for you.`,
211
+ });
212
+ }
213
+ }
214
+ const liveListeners = listeners.filter(l => l.enabled);
215
+ if (liveListeners.length) {
216
+ issues.push({
217
+ severity: 'info',
218
+ code: 'gui-listeners',
219
+ message: `${liveListeners.map(l => `"${l.name}"`).join(', ')} will be switched off for the run.`,
220
+ });
221
+ }
222
+ if (enabledGroups.length && enabledGroups.every(g => !g.sizeProperty)) {
223
+ issues.push({
224
+ severity: 'warning',
225
+ code: 'fixed-thread-count',
226
+ message: 'User counts are fixed inside the plan, so the only way to scale it is more machines.',
227
+ fix: 'Use ${__P(threads,N)} for the thread count to size the run with -Jthreads.',
228
+ });
229
+ }
230
+ else {
231
+ // Say which property, because it is whatever the plan's author chose.
232
+ const props = [...new Set(enabledGroups.map(g => g.sizeProperty).filter(Boolean))];
233
+ if (props.length) {
234
+ issues.push({
235
+ severity: 'info',
236
+ code: 'sizable',
237
+ message: `Size this run with ${props.map(p => `-J${p}=<n>`).join(' ')}.`,
238
+ });
239
+ }
240
+ }
241
+ return {
242
+ threadGroups,
243
+ dataFiles,
244
+ listeners,
245
+ reportingCanAttach: canAttach,
246
+ issues,
247
+ ok: !issues.some(i => i.severity === 'error'),
248
+ };
249
+ }
250
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1,70 @@
1
+ # Atlas load testing
2
+
3
+ This project uses [Atlas](https://atlas.cloudbeat.io) for load testing. The
4
+ `atlas` CLI drives it; `atlas-rec` records scenarios from a browser.
5
+
6
+ Every command takes `--json`, and failures print JSON on standard error with a
7
+ meaningful exit code (`0` pass, `1` a threshold was breached, `2` a usage or
8
+ authentication problem, `3` the run failed or the server was unreachable).
9
+
10
+ ```bash
11
+ atlas schema --json # every command, argument and option
12
+ atlas docs # the documentation, offline
13
+ atlas docs troubleshooting # one topic
14
+ ```
15
+
16
+ ## The flow
17
+
18
+ ```bash
19
+ atlas whoami --json # who am I, which account
20
+ atlas projects --json # what can I see
21
+ atlas files list -p <project> --json # test plans and data files
22
+ atlas files download -p <project> <file> -o ./<file> # before editing anything
23
+ atlas plan check ./<file> -p <project> # will it run, and report?
24
+ atlas files upload -p <project> ./<file> # same name replaces, no version history
25
+ atlas capacity --json # machines, clusters, usage allowance
26
+ atlas run -p <project> --jmx <plan> --servers <n> --wait --gate "error_rate<1,p95<2000"
27
+ atlas results <testId> --json # aggregation report
28
+ atlas timeseries <testId> --json # the shape of the run
29
+ atlas errors <testId> --full # what failed, with request and response
30
+ ```
31
+
32
+ Recording a scenario, driven from the command line:
33
+
34
+ ```bash
35
+ atlas-rec start <url> -o flow.jmx # opens a browser, returns a session
36
+ atlas-rec snapshot # what is on the page
37
+ atlas-rec click --text "Sign in" # act on it
38
+ atlas-rec tx new "Checkout" # name the step that comes next
39
+ atlas-rec stop # write the plan
40
+ atlas-rec suggest flow.capture.json # values that change per session
41
+ ```
42
+
43
+ ## Rules that prevent real damage
44
+
45
+ - **A run costs machine time on someone's account.** Confirm before starting one
46
+ that is not a smoke test, and never stop a run you did not start.
47
+ - **Smoke-run first**: one machine, two users, a minute. Then check the results
48
+ actually contain data before scaling up.
49
+ - **Load multiplies by machines.** Each machine runs the whole plan, so total
50
+ users = users-in-the-plan x machines.
51
+ - **A test that measured nothing looks better than one that worked.** A missing
52
+ thread group with zero errors, a run that ended in seconds, or fast 200s that
53
+ return the login page all mean the test failed, not that the application is
54
+ fast.
55
+ - **Download before you edit or replace a stored file.** Uploading replaces it
56
+ and nothing keeps the old version.
57
+ - **Check a plan before uploading or running it.** A data file the project does
58
+ not have takes out a whole thread group with zero errors to explain it.
59
+ - **Think time is a setting, not a property.** Recorded plans pause between steps
60
+ using a `Settings - Sleeps` variables element; `-J` does not change it. Before
61
+ running a plan locally to debug it, check that element and disable it in your
62
+ working copy, or one user takes minutes instead of seconds. Put it back exactly
63
+ as it was before uploading.
64
+ - **A recording is a draft.** After stopping, report what was captured and ask
65
+ what to do next rather than uploading it. Offer to move the values every user
66
+ would otherwise repeat — credentials, search terms, ids — into a data file.
67
+ - **Report what happened**, including failed gates and uncertainty.
68
+
69
+ Full guidance is in the Atlas skills (`atlas skills install`) and at
70
+ https://atlas.cloudbeat.io/docs.
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: atlas-analyze
3
+ description: Read and explain the results of an Atlas load test - the aggregation report, the shape of the run over time, the errors behind a failure - and recognise the failure modes where a broken test looks like a healthy one. Use when someone asks how a test went, what the numbers mean, why a test failed, or why results look wrong or missing.
4
+ ---
5
+
6
+ # Reading results
7
+
8
+ ```bash
9
+ atlas results <testId> # aggregation report, TOTAL first
10
+ atlas timeseries <testId> # users, response time, error rate over time
11
+ atlas errors <testId> # distinct errors, most common first
12
+ atlas errors <testId> --full # with the request and response behind each
13
+ ```
14
+
15
+ Add `--json` to any of them. `--label "<name>"` narrows timeseries and errors to
16
+ one transaction or request.
17
+
18
+ ## Before interpreting anything, check the test was valid
19
+
20
+ A load test that measured nothing usually looks *better* than one that worked.
21
+ Rule these out first:
22
+
23
+ **Is every thread group present?** Compare the report against the plan. A missing
24
+ thread group with **zero errors** is the classic symptom of a data file the plan
25
+ could not open: every user in that group fails before sending a request, so there
26
+ is nothing to report as an error and the group simply vanishes while the others
27
+ look healthy. The cause is almost always a data file that was never uploaded to
28
+ the project. `atlas plan check -p <project> --jmx <plan>` names it in one line.
29
+ (A path from another machine, `C:\data\users.csv`, is not itself the problem —
30
+ Atlas resolves that to the uploaded file. Nothing uploaded under that name is.)
31
+
32
+ **Did it run for a plausible length of time?** A run that ends in a few seconds
33
+ with an empty dashboard did not run: the plan could not be started at all.
34
+
35
+ **Is the dashboard empty despite a full-length run?** Atlas attaches its
36
+ reporting to the plan when it deploys, and a file with an unusual structure can
37
+ defeat that — the test runs and reports nothing. `atlas plan check` says so
38
+ before you spend the time.
39
+
40
+ **Are the response codes real?** A wall of fast 200s that return the login page
41
+ is a failed test that passes every threshold. Check `atlas errors --full`, and be
42
+ suspicious of response times that are far better than the application's normal
43
+ behaviour.
44
+
45
+ **Did every machine report?** Users should add up to roughly what you asked for
46
+ (users-per-machine x machines). If the peak is a fraction of that, a machine was
47
+ not running.
48
+
49
+ ## Then read the numbers
50
+
51
+ - **Percentiles, not averages.** p95 and p99 are what users feel; an average
52
+ hides the tail completely.
53
+ - **Error rate is errors over requests for the whole test**, not an average of
54
+ per-interval rates. The TOTAL row is authoritative.
55
+ - **Throughput is the result, not the goal.** If throughput flattens while users
56
+ keep climbing, you have found the saturation point — that is usually the
57
+ headline of the whole test.
58
+ - **Look at the shape.** `timeseries` shows where response time started to climb
59
+ and whether errors began at a particular user count. "It broke at about 300
60
+ users" is a more useful finding than any single number.
61
+ - **Per-transaction matters more than TOTAL.** One slow step in an otherwise fast
62
+ plan is invisible in the total and obvious per label.
63
+
64
+ ## Explaining errors
65
+
66
+ `atlas errors --full` gives the actual request and response for each distinct
67
+ error. Sort out which kind you have:
68
+
69
+ - **4xx** — usually the test, not the application: a stale correlated value, an
70
+ expired session, data that does not exist in the target environment.
71
+ - **5xx** — usually the application, and worth reporting with the response body.
72
+ - **Timeouts / connection errors** — the application or something in front of it
73
+ giving up. Check when they started against the user count.
74
+ - **Assertion failures** — the request succeeded but returned the wrong thing,
75
+ such as a login page instead of the page you asked for.
76
+
77
+ A failure that appears only above a certain load is the interesting one. Report
78
+ the level it appeared at.
79
+
80
+ ## Comparing runs
81
+
82
+ ```bash
83
+ atlas tests -p <project> --json # recent runs with peak users, requests, error %
84
+ atlas results <previous> --json
85
+ ```
86
+
87
+ Compare like with like: the same plan, the same number of machines, the same
88
+ user count. A regression is only a regression if the shape of the test was the
89
+ same.
90
+
91
+ ## Writing it up
92
+
93
+ A good report says, in this order:
94
+
95
+ 1. Whether it passed, against the thresholds that were set.
96
+ 2. What load was actually applied — users, ramp, duration, machines.
97
+ 3. Response time under that load: p95 per key transaction, not just the total.
98
+ 4. Where it started to degrade, from the time series.
99
+ 5. What failed, with real examples from the error detail.
100
+ 6. What to do next.
101
+
102
+ State the uncertainty plainly where it exists. "The test never reached the target
103
+ load because one machine dropped out" is a far more useful sentence than a
104
+ confident summary of numbers that do not mean what they appear to.