Skip to content

Commit

Permalink
docs: manual pages and helper scripts to read them
Browse files Browse the repository at this point in the history
  • Loading branch information
klardotsh committed Feb 22, 2022
1 parent d585adb commit f74f5c7
Show file tree
Hide file tree
Showing 7 changed files with 478 additions and 9 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# keep in sync with .dockerignore where appropriate

/target
*.1
*.2
*.3
*.4
*.5
*.6
*.7
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.POSIX:

.SUFFIXES: .1.scd .1 .3.scd .3 .5.scd .5

# teach make how to build man pages (thanks section 1.11 of
# http://khmere.com/freebsd_book/html/ch01.html)
.1.scd.1:
scdoc < $< > $@ || (rm -f $@; exit 1)
.3.scd.3:
scdoc < $< > $@ || (rm -f $@; exit 1)
.5.scd.5:
scdoc < $< > $@ || (rm -f $@; exit 1)

.PHONY: clean
clean:
rm -rf manual/*.1 manual/*.3 manual/*.5

doc: manual
manual: manual/seatrial.1 manual/seatrial.5 manual/seatrial.lua.3
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ indeed, for helping developers make such scale events, Non-Events).

## Usage

> For further detail and commentary, see `man 1 seatrial`, or
> `manual/seatrial.1.scd` in the source tree. While you're at it, there's also the
> following other manual pages, accessible via the same pattern:
>
> - `seatrial(5)`
> - `seatrial.lua(3)`
```
Usage: seatrial <base_url> <req_situation> [<situations...>] [-m <multiplier>]
Expand All @@ -41,6 +34,21 @@ Options:
--help display usage information
```

Further detail, commentary, API documentation, etc. are provided in scdoc
format in the source repo, and in Unix manual page format in installed copies
of `seatrial`.

- `seatrial(1)` documents the CLI application itself
- `seatrial(5)` documents the configuration format
- `seatrial.lua(3)` documents the Lua API to implement dynamic values and
validators

In an installed copy of `seatrial`, including the Docker container, these are
accessible with `man <section> <name>`, for example `man 3 seatrial.lua` or
`docker run --rm -it <container tag> man 3 seatrial.lua`. From this source
tree, provided [scdoc](https://git.sr.ht/~sircmpwn/scdoc) is installed, you can
instead run, for example, `./read_manual_page 'seatrial.lua(3)'`.

## Development and Packaging

Minimum Supported Rust Version is 1.58, as specified in `Cargo.toml`.
Expand Down
4 changes: 2 additions & 2 deletions manual/seatrial.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ writing, such functionality is not yet concretely planned.

# SEE ALSO

*seatrial(5)*++
*seatrial.lua(3)*
- *seatrial(5)*
- *seatrial.lua(3)*

# AUTHORS

Expand Down
302 changes: 302 additions & 0 deletions manual/seatrial.5.scd
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
seatrial(5) "https://engineering.dockwa.com" "situational-mock-based load testing"

# SYNOPSIS

*seatrial(1)* is configured with one declarative, plaintext, RON-formatted file
per Situation to be tested. This manual page provides a quick summary of RON
(Rusty Object Notation - further reading in _SEE ALSO_ below) syntax and a
walkthrough of the fields that make up a *seatrial* Situation config.

# RON SYNTAX

- _structs_ are denoted by an optional name followed by parentheses, and contain
positional or named arguments
- _tuples_ look almost identical to _structs_, but have no preceeding name, and
cannot contain named arguments
- _maps_ are denoted by curly braces, and contain key-value pairs, where the
keys are _strings_ (see below) and the values are of whatever type is
appropriate in the context
- _arrays_ are denoted by brackets; the values within are of whatever type is
appropriate in the context
- _strings_ are double-quoted
- _booleans_ are one of the following literal values:
- true
- false
- _numbers_ follow standard integer and float formatting rules as one would
expect, plus support for 0x and 0b prefixes, for hex and binary numbers,
respectively

# SEATRIAL CONFIGURATION FORMAT

*seatrial* configurations are an unnamed struct, and thus, the entire config
will be wrapped in parentheses (this does mean all useful data is indented a
level if formatted in the typical RON style seen in the wild, but the same is
largely true of JSON anyway).

## lua_file

While optional in the data model, _lua\_file_ is currently a
functionally-required key in the configuration, containing a string path, relative to
the situation file, where the Lua user script (perhaps using *seatrial.lua(3)*
APIs) can be found. This file, when executed in a Lua interpreter, must return a
table with string keys and function values to be used elsewhere in the pipeline.
More on Lua interactions later. For an example of a _lua\_file_, see the
_examples/_ directory in the source tree.

## grunts

_grunts_ is an array of Grunts, *seatrial*'s tongue-in-cheek name for simulated
users. Grunts are simple creatures: they follow the rules provided in their
_persona_ (a _Persona_ struct as described below), and additionally have a
_base\_name_ (a string) and a _count_ (an integer), which together with the
global _multiplier_ (see *seatrial(1)*), determines how many of this Grunt
should be created.

## Persona

_personas_ describe the actions a Grunt will take, and a few other
characteristics regarding how HTTP requests will be made. They are defined in an
anonymous struct as follows:

- _timeout_ is one of the following enum members, and describes the *overall*
timeout that will be applied to HTTP requests within the Persona:
- _Seconds(<integer>)_
- _Milliseconds(<integer>)
- _headers_ is a map of strings to _References_, described below
- _sequence_ is an array of _Actions_, described below

## Persona: References

_References_ are found as the type of numerous _Action_ arguments, as well as
_Persona.headers_ described above. They are enums, and take one of the following
forms:

- _Value(<string>)_ hard-codes a string value, and is often useful for query
parameters, headers, etc. that can be known *statically* (meaning no Lua is
needed to calculate their value). Hard-coded string _Values_ will *always* be
more performant than anything that needs to cross the *seatrial*-Lua boundary,
and thus should be used whenever possible.

- _LuaValue_ plucks the Lua value returned by the last step in the pipeline,
stringifies it, and passes that stringified value to the caller. This is a
fairly niche state, useful when only one parameter/header/etc. in an HTTP
request needs to be dynamic. If the data in the pipe is not a Lua value, doesn't
exist to begin with, or is not stringifiable (notably, functions, tables, and
nil will never be stringified), a fatal error will be thrown and the Grunt will
stop execution.

- _LuaTableIndex(<integer>)_ and _LuaTableValue(<string>)_ pluck a value from
the Lua table returned by the last step in the pipeline by either its numeric
index or its string key, as appropriate. This is almost always the most useful
way to plumb dynamic data to an HTTP request. If the data in the pipe is not a
Lua table, doesn't exist to begin with, if the specified key doesn't exist in
the table, or if the key resolves to a value that cannot be stringified
(notably, functions, tables, and nil will never be stringified), a fatal error
will be thrown and the Grunt will stop execution.

## Persona: Actions

_Actions_ are the proverbial meat and potatoes of *seatrial*, although
properly-seasoned tofu and potatoes also makes for an excellent meal and is
recommended. Each Action in a Sequence *may, optionally*, populate data in the
pipeline for the next step to read. The steps that do so are documented as such
below. Actions are named enum members in RON syntax terms, though some are
double-enumed (think of this as namespacing, perhaps), which is an
implementation detail that may change in a future version (deprecation notices
will be provided).

- _ControlFlow(<action>)_ is a namespace containing only one action:
- _ControlFlow(GoTo(index: <integer>, max_times: <optional integer>))_ jumps
to the specified index in the pipeline, presuming it exists, allowing for
looping and/or skipping of steps. If _max\_times_ is specified, it serves as
an end to the loop after that number of arrivals at _GoTo_

- _Http(<action>)_ is a namespace containing the following actions:
- _Http(Delete(<args>))_
- _Http(Get(<args>))_
- _Http(Head(<args>))_
- _Http(Post(<args>))_
- _Http(Put(<args>))_

Each of these take the same _args_, of which _url_ is required, and the rest
are all optional:

- _url_ is a string containing the relative (to the _base\_url_ provided
at the CLI; see *seatrial(1)*) path to send the request to

- _body_ is a _Reference_ containing the request body. Currently
non-string bodies are relatively untested, and thus not fully defined,
behavior (this is a known issue in *seatrial*)

- _headers_ is a map of strings to _References_ containing HTTP headers.
These are merged with (and in the event of a collision, will override)
the _headers_ of the Persona.

- _params_ is a map of strings to _References_ containing HTTP query
parameters (which are ultimately passed as part of the URL).

- _timeout_ follows the same rules as _Persona.timeout_ and will
override the Persona-provided timeout for this request.

A successful request with *any* status code (not just a 2xx) will be placed
in the pipe for the next step to read (for details on how to access this
from a Lua function, see _LuaFunction_ below). Failures (perhaps due to
timeout, or due to some other system-level failure, like a socket issue)
will immediately end Sequence and Grunt execution, and terminate the thread.

- _LuaFunction(<string>)_ runs the specified Lua function, and places the return
value on the stack. While its implementation in *seatrial* is consistent, it's
also context-specific, so both usecases are documented below. If the Lua
function raises an exception or cannot be found, Grunt execution stops
immediately.

If the data in the pipe is an HTTP request (either by way of this
_LuaFunction_ call being the sole step following an Http(\*) step, or as
part of a _Combinator_, described below), the function will be called with a
table as the sole argument, containing the following properties. This
usecase is almost exclusively useful for _Validators_; the requirements of
_LuaFunction_ in a _Validator_ context is described in _Validator_ below.

- _body_, a table of 8-bit integers representing the raw bytes of the
response body. This will always exist, albeit potentially with a table
length of 0.

- _body\_string_, which will be _nil_ if the body was not parseable as a
UTF-8 string (no other encodings are supported; it's 2022 at time of
writing, use UTF-8 or provide a Lua library to handle other
encodings), or will be the UTF-8 string the body provided, with no
further processing. If the body is, say, JSON, this is the field that
is likely most useful to libraries like _json.lua_ (see _SEE ALSO_
below).

- _content\_type_, a string which contains the Content-Type as dictated
by the returned headers

- _headers_, a table of strings to strings that is unmodified from
whatever the server returned in the response headers

- _status\_code_, a 16-bit integer containing the status code as
provided by the server

If there is no data, or any other type of data, in the pipe, consider the
argument, if any (depending on the version of *seatrial* you have) passed to
the function to be undefined behavior, unstable, and unusable. In this case,
the _LuaFunction_ is likely being used to generate values for future steps
in the Sequence, and should be (within Lua, at least) self-sufficient.

- _Combinator(Combinator([Validator, ...]))_ is a namespace containing three
actions, which allow running multiple _Validators_ (see below) over the same
pipe data:

- _AllOf([Validator, ...])_ runs each listed validator in sequence and
requires that all of them return with an _Ok_ or _OkWithWarnings_
status. If any return with an _Error_ status, execution stops immediately
for the entire persona's pipeline.

- _AnyOf([Validator, ...])_ runs each listed validator in sequence and
requires that one of them return with an _Ok_ or _OkWithWarnings_ status.
This combinator ends once it finds such a status, or fails the entire
pipeline and persona if all listed validators return _Error_.

- _NoneOf([Validator, ...])_ runs each listed validator in sequence and
expects that all of them return an _Error_ status. Immediately upon finding
an _Ok_ or _OkWithWarnings_ status, the pipeline is failed.


- _Validator(<validator>)_ (namespace prefix not allowed in a combinator array;
this is an implementation detail that bleeds through to the UX, sorry) allows
validation of data in the pipe; currently all _Validators_ work on HTTP
responses only. In general, they have one of two prefixes: _WarnUnless_, which
return _Ok_ or _OkWithWarnings_, and _Assert_, which return _Ok_ or immediately
fail the pipeline on _Error_ (barring interactions with _Combinators_, see
above). Further, _LuaFunction_ is a valid _Validator_; functions receive the
data in the pipe as their first argument (see _LuaFunction_ above) and must
return a _ValidationResult_ object as documented in *seatrial.lua(3)*. An
example _LuaFunction_ validator is provided in
_examples/simpleish/seatrial.lua_ in the *seatrial* source tree.

All of the following have _Assert..._ and _WarnUnless..._ forms, so only the
shared parts of their names are listed here for brevity. For example,
_HeaderEquals_ should be expanded to _AssertHeaderEquals_ when writing config
files.

- _HeaderEquals(<string>, <string>)_ takes a case-insensitive header
name and case-sensitive expected value

- _HeaderExists(<string>_ takes a case-insensitive header name, and simply
checks whether it is present in the response at all

- _StatusCode(<u16>)_ takes a 16-bit unsigned integer and checks whether the
HTTP status code exactly matches

- _StatusCodeInRange(<u16>, <u16>)_ takes two 16-bit unsigned integers and
checks whether the HTTP status code is greater than or equal two the
first, *and* is less than or equal to the second

# EXAMPLE

Further examples are provided in the _examples/_ directory in the source tree.

```
(
lua_file: "seatrial.lua",
grunts: [
(
base_name: "Postmaster General",
count: 1,
persona: (
timeout: Seconds(30),
sequence: [
LuaFunction("generate_profile"),
Http(Post(
url: "/profile",
body: LuaTableValue("profile"),
headers: { "Content-Type": Value("application/json") },
)),
Combinator(AllOf([
WarnUnlessStatusCodeInRange(200, 299),
WarnUnlessHeaderExists("X-Never-Gonna-Give-You-Up"),
]))
]
),
),
(
base_name: "Reloader Grunt",
count: 10,
persona: (
timeout: Seconds(30),
sequence: [
LuaFunction("generate_30_day_range"),
Http(Get(
url: "/calendar",
params: {
"start_date": LuaTableValue("start_date"),
"end_date": LuaTableValue("end_date"),
},
)),
Combinator(AllOf([
WarnUnlessStatusCodeInRange(200, 299),
WarnUnlessHeaderExists("X-Never-Gonna-Give-You-Up"),
LuaFunction("is_valid_esoteric_format"),
])),
ControlFlow(GoTo(index: 0, max_times: 2)),
],
),
),
],
)
```

# SEE ALSO

- *seatrial(1)*
- *seatrial.lua(3)*
- https://github.com/ron-rs/ron
- https://github.com/rxi/json.lua

# AUTHORS

Built by Dockwa Engineering. Sources can be found at
https://github.com/dockwa/seatrial.
Loading

0 comments on commit f74f5c7

Please sign in to comment.