From 32462a82507b87651dba7d83f776f7806c6360c9 Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Wed, 1 Jan 2025 14:28:36 +0100 Subject: [PATCH 1/7] fix: Fix missing type declarations --- rebar.config | 6 +++--- src/behaviors/emqttb_behavior_pub.erl | 4 ++-- src/behaviors/emqttb_behavior_sub.erl | 4 ++-- src/emqttb.erl | 6 +++--- src/framework/emqttb_autorate.erl | 6 +++--- src/framework/emqttb_group.erl | 6 +++--- src/framework/emqttb_scenario.erl | 4 ++-- src/framework/emqttb_worker.erl | 4 ++-- src/metrics/emqttb_metrics.erl | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/rebar.config b/rebar.config index 6dc6b05..9d28340 100644 --- a/rebar.config +++ b/rebar.config @@ -3,10 +3,10 @@ {validate_app_modules, true}. {deps, - [ {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.11.0"}}} + [ {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.13.5"}}} , {gproc, "0.9.1"} - , {lee, {git, "https://github.com/k32/lee", {tag, "0.4.4"}}} - , {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe", {tag, "1.0.1"}}} + , {lee, {git, "https://github.com/k32/lee", {tag, "0.5.0"}}} + , {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe", {tag, "1.0.10"}}} , {prometheus, {git, "https://github.com/deadtrickster/prometheus.erl", {tag, "v4.8.2"}}} , hackney , {cowboy, "2.9.0"} diff --git a/src/behaviors/emqttb_behavior_pub.erl b/src/behaviors/emqttb_behavior_pub.erl index 0a24a7a..84d9084 100644 --- a/src/behaviors/emqttb_behavior_pub.erl +++ b/src/behaviors/emqttb_behavior_pub.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ %% API %%================================================================================ --spec model(atom()) -> lee:namespace(). +-spec model(atom()) -> lee:lee_module(). model(Group) -> #{ conn_latency => emqttb_metrics:opstat(Group, connect) diff --git a/src/behaviors/emqttb_behavior_sub.erl b/src/behaviors/emqttb_behavior_sub.erl index 8391b7f..162e2fe 100644 --- a/src/behaviors/emqttb_behavior_sub.erl +++ b/src/behaviors/emqttb_behavior_sub.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ %% API %%================================================================================ --spec model(atom()) -> lee:namespace(). +-spec model(atom()) -> lee:lee_module(). model(GroupId) -> #{ n_received => {[metric], diff --git a/src/emqttb.erl b/src/emqttb.erl index ed12262..9208e36 100644 --- a/src/emqttb.erl +++ b/src/emqttb.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2023, 2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ -type host_selection() :: round_robin | random. --type ifaddr_list() :: list(typerefl:ip_address()). +-type ifaddr_list() :: [inet:ip_address()]. -typerefl_from_string({ifaddr_list/0, ?MODULE, parse_addresses}). -type n_cycles() :: non_neg_integer() | undefined. @@ -123,7 +123,7 @@ duration_to_sleep(DurationUs) when DurationUs >= 1_000 -> duration_to_sleep(DurationUs) -> {1, 1_000 div DurationUs}. --spec get_duration_and_repeats(counters:counters_ref() | non_neg_integer()) -> +-spec get_duration_and_repeats(counters:counters_ref() | emqttb:duration_us()) -> {non_neg_integer(), 1..1000}. get_duration_and_repeats(I) when is_integer(I) -> duration_to_sleep(I); diff --git a/src/framework/emqttb_autorate.erl b/src/framework/emqttb_autorate.erl index 48c6cdc..bf0c766 100644 --- a/src/framework/emqttb_autorate.erl +++ b/src/framework/emqttb_autorate.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -86,7 +86,7 @@ ls(Model) -> {Id, Key} end || Key <- lee_model:get_metatype_index(autorate, Model)]. --spec ensure(config()) -> {auto, counter:counters_ref()}. +-spec ensure(config()) -> {auto, counters:counters_ref()}. ensure(Conf) -> {ok, Pid} = emqttb_autorate_sup:ensure(Conf#{parent => self()}), {auto, get_counter(Pid)}. @@ -96,7 +96,7 @@ get_counter(Autorate) -> gen_server:call(server(Autorate), get_counter). %% Set the current value to the specified value --spec reset(atom() | pid() | lee:ley(), integer()) -> ok. +-spec reset(atom() | pid() | lee:key(), integer()) -> ok. reset(Autorate, Val) -> gen_server:call(server(Autorate), {reset, Val}). diff --git a/src/framework/emqttb_group.erl b/src/framework/emqttb_group.erl index ddea4ad..35ea39f 100644 --- a/src/framework/emqttb_group.erl +++ b/src/framework/emqttb_group.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2023, 2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -80,14 +80,14 @@ set_target(Group, NClients) -> %% fast, not scaling down. Scaling down is rather memory-expensive. %% %% Order of workers' removal during ramping down is not specified. --spec set_target(emqttb:group(), NClients, emqttb:interval() | undefined) -> +-spec set_target(emqttb:group(), NClients, emqttb:duration_us() | undefined) -> {ok, NClients} | {error, new_target} when NClients :: emqttb:n_clients(). set_target(Id, Target, Interval) -> gen_server:call(?via(Id), {set_target, Target, Interval}, infinity). %% @doc Async version of `set_target' --spec set_target_async(emqttb:group(), emqttb:n_clients(), emqttb:interval()) -> ok. +-spec set_target_async(emqttb:group(), emqttb:n_clients(), emqttb:duration_us()) -> ok. set_target_async(Id, Target, Interval) -> gen_server:cast(?via(Id), {set_target, Target, Interval}). diff --git a/src/framework/emqttb_scenario.erl b/src/framework/emqttb_scenario.erl index ab63abe..da29e65 100644 --- a/src/framework/emqttb_scenario.erl +++ b/src/framework/emqttb_scenario.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2023, 2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -130,7 +130,7 @@ complete(PrevStageResult) -> {error, _} -> emqttb:setfail(Scenario) end. --spec model() -> lee_model:lee_module(). +-spec model() -> lee:lee_module(). model() -> application:load(?APP), Scenarios = all_scenario_modules(), diff --git a/src/framework/emqttb_worker.erl b/src/framework/emqttb_worker.erl index 3f0f830..aedf5c8 100644 --- a/src/framework/emqttb_worker.erl +++ b/src/framework/emqttb_worker.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -467,7 +467,7 @@ ifaddr() -> [{ifaddr, IfAddr}] end. --spec ssl_opts() -> [ssl:option()]. +-spec ssl_opts() -> [ssl:tls_option()]. ssl_opts() -> Cert = my_cfg([ssl, certfile]), Keyfile = my_cfg([ssl, keyfile]), diff --git a/src/metrics/emqttb_metrics.erl b/src/metrics/emqttb_metrics.erl index 08f9216..7a429a0 100644 --- a/src/metrics/emqttb_metrics.erl +++ b/src/metrics/emqttb_metrics.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -83,7 +83,7 @@ from_model(ModelKey) -> opstat_from_model(Key) -> {from_model(Key ++ [avg_time]), from_model(Key ++ [pending])}. --spec opstat(atom(), atom()) -> lee:namespace(). +-spec opstat(atom(), atom()) -> lee:lee_module(). opstat(Group, Operation) -> #{ avg_time => {[metric], From 94bc19654fdb552daff0069d82535fe2dbe09a1a Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Wed, 1 Jan 2025 18:50:01 +0100 Subject: [PATCH 2/7] doc: Migrate documentation to TexInfo --- .gitignore | 4 + Makefile | 47 +-- doc/src/emqttb.texi | 62 +++ doc/src/intro.texi | 45 +++ doc/src/schema.adoc | 364 ------------------ doc/src/schema.texi | 239 ++++++++++++ scripts/docgen.escript | 32 +- src/conf/emqttb_conf.erl | 7 +- src/conf/emqttb_conf_model.erl | 86 +++-- src/framework/emqttb_autorate.erl | 49 ++- src/framework/emqttb_scenario.erl | 6 + src/framework/emqttb_worker.erl | 11 +- .../emqttb_scenario_persistent_session.erl | 15 +- src/scenarios/emqttb_scenario_pub.erl | 6 +- src/scenarios/emqttb_scenario_sub.erl | 30 +- 15 files changed, 537 insertions(+), 466 deletions(-) create mode 100644 doc/src/emqttb.texi create mode 100644 doc/src/intro.texi delete mode 100644 doc/src/schema.adoc create mode 100644 doc/src/schema.texi diff --git a/.gitignore b/.gitignore index 0145a15..008c366 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ TAGS /*.tar.gz /emqttb /libquicer_nif.so +doc/lee +doc/info +doc/html +perf.data \ No newline at end of file diff --git a/Makefile b/Makefile index f457bc7..bfb5436 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,16 @@ REBAR ?= $(CURDIR)/rebar3 REBAR_URL ?= https://s3.amazonaws.com/rebar3/rebar3 -XSLTNG := _build/lee_doc/docbook-xslTNG-2.1.2/libs/docbook-xslTNG-2.1.2.jar -DOCBOOK := _build/lee_doc/src/output.xml -MANPAGE_STYLESHEET ?= /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl -WWW := _build/lee_doc/html/index.html -MANPAGE := _build/lee_doc/man/emqttb.1 -CAN_BUILD_DOCS ?= true +TEXINFO := doc/src/emqttb.texi doc/lee/cli_params.texi doc/lee/os_env.texi doc/lee/value.texi + +define MATHJAX_OPTS +loader: {\ + load: ['[tex]/physics'],\ + versionWarnings: false\ +},\ +tex: {\ + packages: {'[+]': ['physics']}\ +} +endef .PHONY: all all: $(REBAR) @@ -32,36 +37,24 @@ $(DOCBOOK): scripts/docgen.escript compile escript scripts/docgen.escript $@ .PHONY: docs -ifeq ($(CAN_BUILD_DOCS), true) -docs: $(MANPAGE) $(WWW) -else -docs: - @echo "!! Docs are not being built" -endif +docs: doc/info/emqttb.info doc/html/index.html -$(MANPAGE): $(DOCBOOK) - xsltproc -o "$$(dirname $<)/../man/" $(MANPAGE_STYLESHEET) "$<" +doc/info/emqttb.info: $(TEXINFO) + texi2any -I doc/lee --info -o $@ $< -$(WWW): $(DOCBOOK) $(XSLTNG) - mkdir -p "$$(dirname $@)" - cd $$(dirname $@) ;\ - java -jar $(CURDIR)/$(XSLTNG) resource-base-uri='./' chunk-output-base-uri='./' \ - verbatim-syntax-highlight-languages='bash erlang' \ - mathml-js='https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js' \ - chunk=index.html persistent-toc=true chunk-nav=true $(CURDIR)/$< - cp -R _build/lee_doc/docbook-xslTNG-2.1.2/resources/* $$(dirname $@) +doc/html/index.html: $(TEXINFO) +# -c MATHJAX_CONFIGURATION="$(MATHJAX_OPTS)" + texi2any -I doc/lee --html -c INFO_JS_DIR=js -c HTML_MATH=mathjax -o doc/html/ $< -$(XSLTNG): - cd _build/lee_doc/ && \ - wget https://github.com/docbook/xslTNG/releases/download/2.1.2/docbook-xslTNG-2.1.2.zip && \ - unzip docbook-xslTNG-2.1.2.zip +$(TEXINFO): compile + ./scripts/docgen.escript doc/lee .PHONY: clean clean: distclean .PHONY: distclean distclean: - @rm -rf _build erl_crash.dump rebar3.crashdump rebar.lock emqttb + @rm -rf _build erl_crash.dump rebar3.crashdump rebar.lock emqttb doc/lee doc/html doc/info $(REBAR): @curl -skfL "$(REBAR_URL)" -o $@ diff --git a/doc/src/emqttb.texi b/doc/src/emqttb.texi new file mode 100644 index 0000000..ee92e3e --- /dev/null +++ b/doc/src/emqttb.texi @@ -0,0 +1,62 @@ +\input texinfo @c -*-texinfo-*- +@c %**start of header +@setfilename emqttb.info +@settitle EMQTTB +@c %**end of header +@copying +A scriptable autotuning MQTT load generator. + +Copyright @copyright{} 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. + +@end copying + +@include schema.texi + +@c The document itself + +@titlepage + @title EMQTTB + @subtitle A scriptable autotuning MQTT load generator + @page + @vskip 0pt plus 1filll + @insertcopying +@end titlepage + +@c Output the table of the contents at the beginning. +@contents + +@ifnottex + @node Top + @top EMQTTB + + @insertcopying +@end ifnottex + +@include intro.texi + +@node Invokation +@chapter Invokation + @node CLI + @section CLI Arguments + @lowersections + @include cli_param.texi + @raisesections + + @node OS Environment Variables + @section OS Environment Variables + @lowersections + @include os_env.texi + @raisesections + +@node All Values +@chapter All Configurable Values + @include value.texi + +@node Index + @unnumbered Index + + @syncodeindex vr cp + @syncodeindex fn cp + @printindex cp + +@bye diff --git a/doc/src/intro.texi b/doc/src/intro.texi new file mode 100644 index 0000000..c7da801 --- /dev/null +++ b/doc/src/intro.texi @@ -0,0 +1,45 @@ +@node Introduction +@chapter Introduction + +@node Topic Patterns +@section Topic Patterns + +@code{emqttb} supports pattern substitution in the topic names. + +@table @samp + @item %n + replaced with the worker ID (integer) + @item %g + replaced with the group ID + @item %h + replaced with the hostname +@end table + + +@node Verify Message Sequence +@section Message Sequence Verification + +@code{emqttb} has builtin tools for detecting message loss and repetition. + +@quotation Warning +Publishers should insert metadata into the payloads in order for this feature to work. +@end quotation + +@quotation Warning +This feature can use a lot of RAM to store the sequence numbers for each triple of sender client id, receiver client id, and MQTT topic. +@end quotation + +Errors about missing messages and warnings about duplicate messages are printed to the emqttb log. + +@heading Prometheus metrics + +@table @code +@item emqttb_repeats_number +number of times when the sequence number of the message goes backwards +@item emqttb_gaps_number +number of times when the sequence number of the message skips the messages (a gap) +@item emqttb_repeat_size +rolling average; size of the repeated sequence +@item emqttb_gap_size +rolling average; size of the gap +@end table diff --git a/doc/src/schema.adoc b/doc/src/schema.adoc deleted file mode 100644 index 2bccd3b..0000000 --- a/doc/src/schema.adoc +++ /dev/null @@ -1,364 +0,0 @@ -:!sectids: -:stem: -= Documentation - -[id=cluster.node_name] -== Node name - -Note: erlang distribution is disabled when node name is `undefined`. - -[id=restapi.enabled] -== Enable REST API -`+--restapi+` CLI argument enables REST API (by default it's available at http://127.0.0.0:8017), and it also means that the script keeps running after completing the scenarios. - -[id=autorate] -== Autorate configuration - -When the loadgen creates too much traffic, the system may get overloaded. -In this case, the test usually has to be restarted all over again with different parameters. -This can be very expensive in man-hours and computing resources. - -In order to prevent that, emqttb can tune some parameters (such as message publishing interval) -automatically using https://controlguru.com/integral-reset-windup-jacketing-logic-and-the-velocity-pi-form/[PI controller]. - -The following formula is used for the error function: - -stem:[e=(a_{SP} - a_{PV}) a_{coeff}] - -=== Autoscale - -A special autorate controlling the rate of spawning new clients is implicitly created for each client group. -Its name usually follows the pattern `%scenario%/conn_interval`. - - -By default, the number of pending (unacked) connections is used as the process variable. -Number of pending connections is a metric that responds very fast to target overload, so it makes a reasonable default. - -For example the following command can automatically adjust the rate of connections: - -[code,bash] ----- -./emqttb --pushgw @conn -I 10ms -N 5_000 \ - @a -a conn/conninterval -V 1000 --setpoint 10 ----- - - -[id=autorate._.id] -== ID of the autorate configuration - -Autorate configuration can be referred by id. -This value must be equal to one of the elements returned by `emqttb @ls autorate` command. -Full list is also available in <> - - -[id=autorate._.speed] -== Maximum rate of change of the controlled parameter - -Note: by default this parameter is set to 0 for each autorate, effectively locking the control parameter in place. - - -[id=autorate._.process_variable] -== Process variable - -This parameter specifies ID of the metric that senses pressure on the SUT and serves as the process variable (PV). -Its value must be equal to one of the metric IDs returned by `emqttb @ls metric` command. -Full list can be also found in <>. - -[id=autorate._.setpoint] -== Setpoint - -The desired value of the process variable (PV) is called the setpoint. -Autorate adjusts the value of the control variable (CV) to bring the PV close to the setpoint. - - -[id=interval] -== Default interval between events - -Supported units: - -* `us`: microseconds -* `ms`: milliseconds -* `s`: seconds -* `min`: minutes -* `h` : hours - -If unit is not specified then `ms` is assumed. - -[id=scenarios.sub] -== Run scenario sub - -This scenario starts `-N` workers, which subscribe to a specified topic. -The only mandatory parameter is `--topic`, which supports pattern substitutions. - -[id=scenarios.sub._.clean_start] -== Clean start -Note: in order to disable clean start (and make the session persistent) this flag should be set to `false` (for example, `./emqttb @sub +c ...` via CLI). - -[id=scenarios.sub._.parse_metadata] -== Extract metadata from message payloads - -Subscribers will report end-to-end latency when this option is enabled. - -WARNING: Publishers should insert metadata into the payloads. -For example, when using <> it's necessary to enable <> generation. - -WARNING: In order to measure latency accurately, the scenario should ensure that publishers reside on the same emqttb host with the subscribers. -Otherwise clock skew between different load generator instances will introduce a systematic error. - - -[id=scenarios.sub._.verify_sequence] -== Verify sequence of messages - -When this option is enabled, emqttb will parse the metadata embedded in the messages and check for missing or duplicated messages. -This option implies <>. - -Errors about missing messages and warnings about duplicate messages are printed to the `emqttb.log`. -Relevant prometheus metrics include: - -- `emqttb_repeats_number` -- number of times when the sequence number of the message goes backwards -- `emqttb_gaps_number` -- number of times when the sequence number of the message skips the messages (a gap) -- `emqttb_repeat_size` -- rolling average; size of the repeated sequence -- `emqttb_gap_size` -- rolling average; size of the gap - - -WARNING: Publishers should insert metadata into the payloads in order for this feature to work. - -WARNING: This feature can use a lot of RAM to store the sequence numbers for each triple of sender client id, receiver client id, and MQTT topic. - -=== Client groups - -- `sub` - -[id=scenarios.conn] -== Run scenario conn - -This scenario starts `-N` workers, which connect to the broker and then simply linger around. - -=== Client groups - -- `conn` - - -[id=scenarios.pub] -== Run scenario pub - -This scenario starts `-N` workers, which publish messages to the specified topic at period `--pubinterval`. -The only mandatory parameter is `--topic`, which supports pattern substitutions. - -=== Client groups - -- `pub` - -=== Examples -==== Basic usage - -[code,bash] ----- -emqttb @pub -t foo/%n -N 100 -i 10ms -s 1kb ----- - -In this example the loadgen connects to the default broker , -starts 100 publishers which send messages to topic with the suffix of the worker id every 10 milliseconds. Size of the messages is 1kb. - -==== Changing client settings - -[code,bash] ----- -emqttb @pub -t foo/%n @g --ssl --transport ws -h 127.0.0.1 ----- - -In this example settings of the default client group has been changed: TLS encryption is enabled, and WebSocket transport is used. -Also the hostname of the broker is specified explicitly. - -[code,bash] ----- -emqttb @pub -t foo/%n -q 1 -g pub @g -g pub --ssl --transport ws -h 127.0.0.1 ----- - -The below example is similar to the previous one, except QoS of the messages is set to 1, -and a dedicated client configuration with id `pub` is used for the publishers. -It's useful for running multiple scenarios (e.g. `@pub` and `@sub`) in parallel, when they must use -different settings. For example, it can be used for testing MQTT bridge. - - -==== Tuning publishing rate automatically - -By default, `@pub` scenario keeps `pubinterval` constant. -However, in some situations it should be tuned dynamically: suppose one wants to measure what publishing rate the broker can sustain while keeping publish latency under `--publatency`. - -This is also useful for preventing system overload. -Generating too much load can bring the system down, and the test has to be started all over again with different parameters. -Sometimes finding the correct rate takes many attempts, wasting human and machine time. -Dynamic tuning of the publishing rate for keeping the latency constant can help in this situation. - -By default the maximum speed of rate adjustment is set to 0, effectively locking the `pubinterval` at a constant value. -To enable automatic tuning, the autorate speed `-V` must be set to a non-zero value, also it makes sense to set -the minimum (`-m`) and maximum (`-M`) values of the autorate: - -[code,bash] ----- -emqttb @pub -t foo -i 1s -q 1 --publatency 50ms @a -V 10 -m 0 -M 10000 ----- - -Once automatic adjustment of the publishing interval is enabled, `-i` parameter sets the starting value of the publish interval, -rather than the constant value. So the above example reads like this: - -Publish messages to topic `foo` with QoS 1, starting at the publishing interval of 1000 milliseconds, dynamically adjusting it -so to keep the publishing latency around 50 milliseconds. The publishing interval is kept between 0 and 10 seconds, -and the maximum rate of its change is 10 milliseconds per second. - -=== Client groups -- `pub` - -[id=scenarios.pub._.topic] -== Topic where the clients shall publish messages - -Topic is a mandatory parameter. It supports the following substitutions: - -* `%n` is replaced with the worker ID (integer) -* `%g` is replaced with the group ID -* `%h` is replaced with the hostname - - -[id=scenarios.pub._.clean_start] -== Clean start -Set https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901039[clean start] flag in the CONNECT packet. - -[id=scenarios.pub._.expiry] -== Session Expiry Interval -Add https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901048[Session Expiry Interval] property to the CONNECT packet. - - -[id=scenarios.pubsub_forward] -== run scenario pubsub_forward - -First all subscribers connect and subscribe to the brokers, then the -publishers start to connect and publish. The default is to use full -forwarding of messages between the nodes: that is, each publisher -client publishes to a topic subscribed by a single client, and both -clients reside on distinct nodes. - -Full forwarding of messages is the default and can be set by full_forwarding. - -=== Examples -==== Basic usage - -[code,bash] ----- -./emqttb --restapi @pubsub_fwd --publatency 10ms --num-clients 400 -i 70ms \ - @g -h 172.25.0.2:1883,172.25.0.3:1883,172.25.0.4:1883 ----- - -In this example the loadgen connects to a list of brokers -in a round-robin in the declared order. First all the -subscribers, then the publishers, with the difference that -publishers will shift the given host list by one position -to ensure each publisher and subscriber pair will reside -on different hosts, thus forcing all messages to be -forwarded. - -=== Client groups - -- `pubsub_forward.pub` -- `pubsub_forward.sub` - -[id=scenarios.persistent_session] - -== Run scenario persistent_session - -This scenario measures throughput of MQTT broker in presence of persistent sessions. -It is split in two stages that repeat in a loop: - -- `consume` stage where subscribers (re)connect to the broker with `clean_session=false` and ingest saved messages -- `publish` stage where subscribers disconnect, and another group of clients publishes messages to the topics - -This separation helps to measure throughput of writing and reading messages independently. - -Publish stage runs for a <>. -It's possible to adjust publishing rate via autorate. - -Consume stages runs until the subscribers ingest all published messages, -or until <>. -Please note that throughput measurement is not reliable when the consume stage is aborted due to timeout. - -=== Examples - -=== Client groups - -- `persistent_session.pub` -- `persistent_session.sub` - -[id=scenarios.persistent_session._.pub.qos] -== QoS of the published messages - -WARNING: changing QoS to any value other then 2 is likely to cause consume stage to hang, -since it has to consume the exact number of messages as previously produced. - -[id=scenarios.persistent_session._.sub.qos] -== Subscription QoS - -WARNING: changing QoS to any value other then 2 is likely to cause consume stage to hang, -since it has to consume the exact number of messages as previously produced. - -[id=groups] -== Configuration for client groups -Client configuration is kept separate from the scenario config. -This is done so scenarios could share client configuration. - -[id=groups._.net.ifaddr] -== Local IP addresses - -Bind a specific local IP address to the connection. -If multiple IP addresses are given, workers choose local address using round-robin algorithm. - -WARNING: Setting a local address for a client TCP connection explicitly has a nasty side effect: -when you do this `gen_tpc` calls `bind` on this address to get a free ephemeral port. -But the OS doesn't know that in advance that we won't be listening on the port, so it reserves the local port number for the connection. -However, when we connect to multiple EMQX brokers, we do want to reuse local ports. -So don't use this option when the number of local addresses is less than the number of remote addresses. - - -[id=groups._.client.clientid] -== Clientid pattern - -Pattern used to generate ClientID. -The following substitutions are supported: - -* `%n` is replaced with the worker ID (integer) -* `%g` is replaced with the group ID -* `%h` is replaced with the hostname - - -[id=groups._.connection.keepalive] -== Keepalive time - -How often the clients will send `PING` MQTT message to the broker on idle connections. - -[id=autorate._.scram.enabled] -== SCRAM -Normally, autorate adjusts the control variable gradually. -However, sometimes the system under test becomes overloaded suddenly, and in this case slowly decreasing the pressure may not be efficient enough. -To combat this situation, `emqttb` has "SCRAM" mechanism, that immediately resets the control variable to a <>. -This happens when the value of process variable exceeds the <>. - -SCRAM mode remains in effect until the number of pending connections becomes less than -_threshold_ * <> / 100. - - -[id=autorate._.scram.threshold] -== SCRAM threshold - - -[id=autorate._.scram.override] -== SCRAM rate override -Replace configured (or calculated via autorate) value of the control variable with this value when the system under test is not keeping up with the load. - - -[id=autorate._.scram.hysteresis] -== SCRAM hysteresis -It's not desirable to switch between normal and SCRAM connection rate too often. - -[id=autorate._.update_interval] -== How often autorate is updated - -This parameter governs how often error is calculated and control parameter is updated. diff --git a/doc/src/schema.texi b/doc/src/schema.texi new file mode 100644 index 0000000..234493a --- /dev/null +++ b/doc/src/schema.texi @@ -0,0 +1,239 @@ +@c Put long definitions here + +@macro doc-clientid + Pattern used to generate ClientID. + + The following substitutions are supported: + + @table @samp + @item %n + replaced with the worker ID (integer) + @item %g + replaced with the group ID + @item %h + replaced with the hostname + @end table + +@end macro + +@macro doc-ifaddr + Bind a specific local IP address to the connection. + If multiple IP addresses are given, workers choose local address using round-robin algorithm. + + @quotation Warning + Setting a local address for a client TCP connection explicitly has a nasty side effect: + when you do this @code{gen_tpc} calls @code{bind} on this address to get a free ephemeral port. + But the OS doesn't know that in advance that we won't be listening on the port, so it reserves the local port number for the connection. + However, when we connect to multiple EMQX brokers, we do want to reuse local ports. + So don't use this option when the number of local addresses is less than the number of remote addresses. + @end quotation + +@end macro + +@macro doc-autorate + When the loadgen creates too much traffic, the system may get overloaded. + In this case, the test usually has to be restarted all over again with different parameters. + This can be very expensive in man-hours and computing resources. + + In order to prevent that, emqttb can tune some parameters (such as message publishing interval) + automatically using + @url{https://controlguru.com/integral-reset-windup-jacketing-logic-and-the-velocity-pi-form/,PI controller}. + + The following formula is used for the error function: + + @math{e=(a_{SP} - a_{PV}) a_{coeff}} + + @heading Autoscale + @cindex autoscale + + A special autorate controlling the rate of spawning new clients is implicitly created for each client group. + Its name usually follows the pattern @code{%scenario%/conn_interval}. + + + By default, the number of pending (unacked) connections is used as the process variable. + Number of pending connections is a metric that responds very fast to target overload, so it makes a reasonable default. + + For example the following command can automatically adjust the rate of connections: + + @example + ./emqttb --pushgw @@conn -I 10ms -N 5_000 \ + @@a -a conn/conninterval -V 1000 --setpoint 10 + @end example + +@end macro + +@macro doc-scram + Normally, autorate adjusts the control variable gradually. + However, sometimes the system under test becomes overloaded suddenly, and in this case slowly decreasing the pressure may not be efficient enough. + To combat this situation, @code{emqttb} has "SCRAM" mechanism, that immediately resets the control variable to a @ref{value/autorate/_/scram/override,configured safe value}. + This happens when the value of process variable exceeds a @ref{value/autorate/_/scram/threshold,certain threshold}. + + SCRAM mode remains in effect until the number of pending connections becomes less than @math{\frac{t h}{100}} + where @math{t} is threshold, and FIXME @math{h} is @ref{value/autorate/_/scram/hysteresis,hystersis}. + +@end macro + +@macro doc-interval + Supported units: + + @table @samp + @item us + microseconds + @item ms + milliseconds + @item s + seconds + @item min + minutes + @item h + hours + @end table + + If unit is not specified then @samp{ms} is assumed. + +@end macro + +@macro doc-scenario-sub + This scenario starts @code{-N} workers, which subscribe to a specified topic. + The only mandatory parameter is @code{--topic}, which supports pattern substitutions. + + @heading Client groups + @code{sub} + +@end macro + +@macro doc-scenario-conn + This scenario starts @code{-N} workers, which connect to the broker and then simply linger around. + + @heading Client groups + @code{conn} + +@end macro + +@macro doc-scenario-pub + This scenario starts @code{-N} workers, which publish messages to the specified topic at period @code{--pubinterval}. + The only mandatory parameter is @code{--topic}, which supports pattern substitutions. + + @heading Client groups + @code{pub} + + @heading Basic usage example + @example + emqttb @@pub -t foo/%n -N 100 -i 10ms -s 1kb + @end example + + + In this example the loadgen connects to the default broker @url{mqtt://localhost:1883}, + starts 100 publishers which send messages to topic with the suffix of the worker id every 10 milliseconds. + Size of the messages is 1kb. + + @heading Changing client settings + @example + emqttb @@pub -t foo/%n @@g --ssl --transport ws -h 127.0.0.1 + @end example + + In this example settings of the default client group has been changed: TLS encryption is enabled, and WebSocket transport is used. + Also the hostname of the broker is specified explicitly. + + @example + emqttb @@pub -t foo/%n -q 1 -g pub @@g -g pub --ssl --transport ws -h 127.0.0.1 + @end example + + The above example is similar to the previous one, except QoS of the messages is set to 1, + and a dedicated client configuration with id `pub` is used for the publishers. + It's useful for running multiple scenarios (e.g. @code{@@pub} and @code{@@sub}) in parallel, when they must use different settings. + For example, it can be used for testing MQTT bridge. + + @heading Tuning publishing rate automatically + + By default, @code{@@pub} scenario keeps @code{pubinterval} constant. + However, in some situations it should be tuned dynamically: + suppose one wants to measure what publishing rate the broker can sustain while keeping publish latency under @code{--publatency}. + + This is also useful for preventing system overload. + Generating too much load can bring the system down, and the test has to be started all over again with different parameters. + Sometimes finding the correct rate takes many attempts, wasting human and machine time. + Dynamic tuning of the publishing rate for keeping the latency constant can help in this situation. + + By default the maximum speed of rate adjustment is set to 0, effectively locking the @code{pubinterval} at a constant value. + To enable automatic tuning, the autorate speed @code{-V} must be set to a non-zero value, also it makes sense to set + the minimum (@code{-m}) and maximum (@code{-M}) values of the autorate: + + @example + emqttb @@pub -t foo -i 1s -q 1 --publatency 50ms @@a -V 10 -m 0 -M 10000 + @end example + + Once automatic adjustment of the publishing interval is enabled, @code{-i} parameter sets the starting value of the publish interval, + rather than the constant value. So the above example reads like this: + + Publish messages to topic @code{foo} with QoS 1, starting at the publishing interval of 1000 milliseconds, dynamically adjusting it + so to keep the publishing latency around 50 milliseconds. The publishing interval is kept between 0 and 10 seconds, + and the maximum rate of its change is 10 milliseconds per second. + +@end macro + +@macro doc-scenario-pubsub-fwd + First all subscribers connect and subscribe to the brokers, then the publishers start to connect and publish. + The default is to use full forwarding of messages between the nodes: + that is, each publisher client publishes to a topic subscribed by a single client, and both clients reside on distinct nodes. + + Full forwarding of messages is the default and can be set by full_forwarding. + + @heading Client Groups + @itemize + @item + @code{pubsub_forward.pub} + @item + @code{pubsub_forward.sub} + @end itemize + + @heading Basic Usage + + @example + ./emqttb --restapi @@pubsub_fwd --publatency 10ms --num-clients 400 -i 70ms \ + @@g -h 172.25.0.2:1883,172.25.0.3:1883,172.25.0.4:1883 + @end example + + In this example the loadgen connects to a list of brokers in a round-robin in the declared order. + First all the subscribers, then the publishers, + with the difference that publishers will shift the given host list by one position to ensure each publisher and subscriber pair will reside on different hosts, + thus forcing all messages to be forwarded. + +@end macro + +@macro doc-scenario-persistent-session + This scenario starts @code{-N} workers, which connect to the broker and then simply linger around. + + This scenario measures throughput of MQTT broker in presence of persistent sessions. + It is split in two stages that repeat in a loop: + + @enumerate + @item + @samp{consume} stage where subscribers (re)connect to the broker with `clean_session=false` and ingest saved messages + @item + @samp{publish} stage where subscribers disconnect, and another group of clients publishes messages to the topics + @end enumerate + + This separation helps to measure throughput of writing and reading messages independently. + + Publish stage runs for a @ref{value/scenarios/persistent_session/_/pub/pub_time,set period of time}. + It's possible to adjust publishing rate via autorate. + + Consume stages runs until the subscribers ingest all published messages, + or until @ref{value/scenarios/persistent_session/_/max_stuck_time,timeout}. + Please note that throughput measurement is not reliable when the consume stage is aborted due to timeout. + + @heading Client Groups + @itemize + @item + @code{persistent_session.pub} + @item + @code{persistent_session.sub} + @end itemize + +@end macro + +@macro doc-scenario-sub-flapping + FIXME + +@end macro diff --git a/scripts/docgen.escript b/scripts/docgen.escript index 96340a5..535cdba 100755 --- a/scripts/docgen.escript +++ b/scripts/docgen.escript @@ -1,13 +1,23 @@ #!/usr/bin/escript -%%! -pa _build/default/lib/lee/ebin -pa _build/default/lib/typerefl/ebin -pa _build/default/lib/emqttb/ebin +%%! -pa _build/default/lib/lee/ebin -pa _build/default/lib/typerefl/ebin -pa _build/default/lib/emqttb/ebin -pa ./_build/default/checkouts/lee/ebin %% -*- erlang -*- -main(OutFile) -> - AsciidocOptions = #{}, - ok = filelib:ensure_dir(OutFile), - io:format(user, "Enriching model...~n", []), - RawModel = lee_asciidoc:enrich_model(AsciidocOptions, emqttb_conf_model:model()), - io:format(user, "Compiling model...~n", []), - {ok, Model} = emqttb_conf:compile_model(RawModel), - io:format(user, "Dumping XML...~n", []), - ok = lee_doc:make_docs(Model, #{output_file => OutFile}), - io:format(user, "Done, exiting.~n", []). + +main(OutDir) -> + ExtractorConfig = #{ output_dir => OutDir + , extension => ".texi" + , formatter => fun lee_doc:texinfo/3 + , metatypes => [cli_param, value, os_env] + }, + ok = emqttb_conf:load_model(), + [_|_] = lee_doc:make_docs(emqttb_conf:model(), ExtractorConfig). + +%% main(OutFile) -> +%% AsciidocOptions = #{}, +%% ok = filelib:ensure_dir(OutFile), +%% io:format(user, "Enriching model...~n", []), +%% RawModel = lee_asciidoc:enrich_model(AsciidocOptions, emqttb_conf_model:model()), +%% io:format(user, "Compiling model...~n", []), +%% {ok, Model} = emqttb_conf:compile_model(RawModel), +%% io:format(user, "Dumping XML...~n", []), +%% ok = lee_doc:make_docs(Model, #{output_file => OutFile}), +%% io:format(user, "Done, exiting.~n", []). diff --git a/src/conf/emqttb_conf.erl b/src/conf/emqttb_conf.erl index fa90425..2ab120d 100644 --- a/src/conf/emqttb_conf.erl +++ b/src/conf/emqttb_conf.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2023, 2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ -module(emqttb_conf). %% API: --export([load_model/0, load_conf/0, get/1, list_keys/1, reload/0, patch/1, string2patch/1]). +-export([load_model/0, model/0, load_conf/0, get/1, list_keys/1, reload/0, patch/1, string2patch/1]). -export([compile_model/1]). -export_type([]). @@ -33,6 +33,9 @@ %% API funcions %%================================================================================ +model() -> + ?MYMODEL. + load_model() -> case compile_model(emqttb_conf_model:model()) of {ok, Model} -> diff --git a/src/conf/emqttb_conf_model.erl b/src/conf/emqttb_conf_model.erl index 735c7a4..848392a 100644 --- a/src/conf/emqttb_conf_model.erl +++ b/src/conf/emqttb_conf_model.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2023, 2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -35,22 +35,20 @@ %%================================================================================ model() -> - #{ '$doc_root' => - {[doc_root], - #{ oneliner => "A scriptable load generator for MQTT" - , app_name => "EMQTT bench daemon" - , prog_name => "emqttb" - }} - , cluster => + #{ cluster => #{ node_name => {[value, os_env], - #{ type => atom() + #{ oneliner => "Node name" + , doc => "Note: erlang distribution is disabled when node name is @code{undefined}.\n" + , type => atom() , default => undefined }} } , interval => {[value, cli_param], - #{ type => emqttb:duration_us() + #{ oneliner => "Default interval between events" + , doc => "@doc-interval" + , type => emqttb:duration_us() , default_str => "10ms" , cli_operand => "max-rate" , cli_short => $R @@ -72,7 +70,18 @@ model() -> }} } , restapi => - #{ listen_port => + #{ enabled => + {[value, os_env, cli_param], + #{ oneliner => "Enable REST API" + , doc => "@option{--restapi} CLI argument enables REST API + (available at @url{http://127.0.0.0:8017} by default), + and it also means that the script keeps running after completing the scenarios. + " + , type => boolean() + , default => false + , cli_operand => "restapi" + }} + , listen_port => {[value, os_env, cli_param], #{ oneliner => "REST API listening interface/port" , type => typerefl:listen_port_ip4() @@ -85,12 +94,6 @@ model() -> , type => boolean() , default => false }} - , enabled => - {[value, os_env, cli_param], - #{ type => boolean() - , default => false - , cli_operand => "restapi" - }} } , logging => #{ level => @@ -199,11 +202,10 @@ model() -> #{ again => {[value, cli_param], #{ oneliner => "Repeat the last execution" - , doc => " - Note: it tries best to restore the previous environment, - so it only makes sense to use this option alone, as - it overrides other options. - " + , doc => "Note: it tries best to restore the previous environment, + so it only makes sense to use this option alone, as + it overrides other options. + " , type => boolean() , default => false , cli_operand => "again" @@ -211,13 +213,11 @@ model() -> , conf_dump => {[value, os_env, cli_param], #{ oneliner => "Name of the repeat file or `undefined`" - , doc => " - If set to a string value, emqttb will dump its configuration - to a \"repeat\" file that can be used to quickly repeat the last run. - - - Note: only the successful runs of the script are saved. - " + , doc => "If set to a string value, emqttb will dump its configuration + to a \"repeat\" file that can be used to quickly repeat the last run. + + Note: only the successful runs of the script are saved. + " , type => union([undefined, string()]) , default => ".emqttb.repeat" , cli_operand => "conf-dump-file" @@ -240,16 +240,14 @@ model() -> , keep_running => {[value, os_env, cli_param], #{ oneliner => "Keep the process running after completing all the scenarios" - , doc => " - By default, when started without REST API, emqttb script terminates - after completing all the scenarios, which is useful for scripting. - However, when running with REST API, such behavior is undesirable. - So when REST is enabled, the default behavior is different: the - process keeps running waiting for commands. - - - This flag can be used to explicitly override this behavior. - " + , doc => "By default, when started without REST API, emqttb script terminates + after completing all the scenarios, which is useful for scripting. + However, when running with REST API, such behavior is undesirable. + So when REST is enabled, the default behavior is different: the + process keeps running waiting for commands. + + This flag can be used to explicitly override this behavior. + " , type => boolean() , default_ref => [restapi, enabled] , cli_operand => "keep-running" @@ -273,13 +271,19 @@ model() -> } , groups => {[map, cli_action, default_instance], - #{ cli_operand => "g" + #{ oneliner => "Client configuration" + , doc => "Client configuration is kept separate from the scenario config. + This is done so scenarios could share client configuration. + " + , cli_operand => "g" , key_elements => [[id]] }, emqttb_worker:model()} , autorate => {[map, cli_action], - #{ cli_operand => "a" + #{ oneliner => "Autorate configuration" + , doc => "@doc-autorate" + , cli_operand => "a" , key_elements => [[id]] }, emqttb_autorate:model()} diff --git a/src/framework/emqttb_autorate.erl b/src/framework/emqttb_autorate.erl index bf0c766..5fadfe8 100644 --- a/src/framework/emqttb_autorate.erl +++ b/src/framework/emqttb_autorate.erl @@ -128,14 +128,24 @@ start_link(Conf = #{id := Id}) -> model() -> #{ id => {[value, cli_param, autorate_id], - #{ type => atom() + #{ oneliner => "ID of the autorate configuration" + , doc => "Autorate identifier. + This value must be equal to one of the elements returned by @code{emqttb @@ls autorate} command. + Full list is also available in FIXME <> + " + , type => atom() , default => default , cli_operand => "autorate" , cli_short => $a }} , process_variable => {[value, cli_param, metric_id], - #{ oneliner => "Key of the metric that the autorate uses as a process variable" + #{ oneliner => "Process variable" + , doc => "This parameter specifies ID of the metric that senses pressure on the SUT and serves as the process variable (PV). + Its value must be equal to one of the metric IDs returned by @code{emqttb @@ls metric} command. + + Full list can be also found in FIXME <>. + " , type => lee:model_key() , cli_operand => "pvar" }} @@ -148,7 +158,10 @@ model() -> }} , set_point => {[value, cli_param], - #{ oneliner => "Value of the process variable that the autorate will try to keep" + #{ oneliner => "Set point" + , doc => "The desired value of the process variable (PV) is called the setpoint. + Autorate adjusts the value of the control variable (CV) to bring the PV close to the setpoint. + " , type => number() , default => 0 , cli_operand => "setpoint" @@ -172,14 +185,18 @@ model() -> }} , speed => {[value, cli_param], - #{ type => non_neg_integer() + #{ oneliner => "Speed" + , doc => "Maximum rate of change of the controlled parameter. + + Note: by default this parameter is set to 0 for each autorate, effectively locking the control parameter in place." + , type => non_neg_integer() , default => 0 , cli_operand => "speed" , cli_short => $V }} , k_p => {[value, cli_param], - #{ oneliner => "Controller gain" + #{ oneliner => "Controller gain, @math{k_p}" , type => number() , default => 0.05 , cli_operand => "Kp" @@ -187,7 +204,7 @@ model() -> }} , t_i => {[value, cli_param], - #{ oneliner => "Controller reset time" + #{ oneliner => "Controller reset time, @math{t_i}" , type => number() , default => 1 , cli_operand => "Ti" @@ -195,7 +212,9 @@ model() -> }} , update_interval => {[value, cli_param], - #{ type => emqttb:duration_ms() + #{ oneliner => "Autorate update interval" + , doc => "This parameter governs how often error is calculated and control parameter is updated.\n" + , type => emqttb:duration_ms() , default => 100 , cli_operand => "update-interval" , cli_short => $u @@ -203,7 +222,9 @@ model() -> , scram => #{ enabled => {[value, cli_param], - #{ type => boolean() + #{ oneliner => "Enable SCRAM" + , doc => "@doc-scram" + , type => boolean() , default => false , cli_operand => "olp" }} @@ -215,14 +236,22 @@ model() -> }} , hysteresis => {[value, cli_param], - #{ oneliner => "Hysteresis (%) of overload detection" + #{ oneliner => "SCRAM hysteresis" + , doc => "Hysteresis is defined as percent of the threshold. + + It is used to prevent frequent switching between normal and SCRAM modes of operation. + " , type => typerefl:range(1, 100) , default => 50 , cli_operand => "olp-hysteresis" }} , override => {[value, cli_param], - #{ type => emqttb:duration_us() + #{ oneliner => "SCRAM rate override" + , doc => "Replace configured (or calculated via autorate) value of the control variable with this value + when the system under test is not keeping up with the load. + " + , type => emqttb:duration_us() , default_str => "10s" , cli_operand => "olp-override" }} diff --git a/src/framework/emqttb_scenario.erl b/src/framework/emqttb_scenario.erl index da29e65..3c0a5c5 100644 --- a/src/framework/emqttb_scenario.erl +++ b/src/framework/emqttb_scenario.erl @@ -238,6 +238,7 @@ make_model(Module) -> #{ cli_operand => atom_to_list(Name) , key_elements => [] , oneliner => lists:concat(["Run scenario ", Name]) + , doc => doc_macro(Module) }, maps:merge( #{ loiter => @@ -258,3 +259,8 @@ all_scenario_modules() -> , M:module_info() ), ?MODULE <- Behaviors]. + +doc_macro(Module) -> + <<"emqttb_scenario_", Rest/binary>> = atom_to_binary(Module), + Name = binary:replace(Rest, <<"_">>, <<"-">>, [global]), + <<"@doc-scenario-", Name/binary>>. diff --git a/src/framework/emqttb_worker.erl b/src/framework/emqttb_worker.erl index aedf5c8..432447c 100644 --- a/src/framework/emqttb_worker.erl +++ b/src/framework/emqttb_worker.erl @@ -305,16 +305,20 @@ model() -> }} , keepalive => {[value, cli_param], - #{ type => emqttb:duration_s() + #{ oneliner => "Keepalive time" + , doc => "How often the clients will send @code{PING} MQTT message to the broker on idle connections." + , type => emqttb:duration_s() , default_str => "60s" , cli_operand => "keepalive" - , cli_short => $k + , cli_short => $k }} } , client => #{ clientid => {[value, cli_param], - #{ type => binary() + #{ oneliner => "ClientID pattern" + , doc => "@doc-clientid" + , type => binary() , default => <<"%h-%g-%n">> , cli_operand => "clientid" , cli_short => $i @@ -340,6 +344,7 @@ model() -> #{ ifaddr => {[value, cli_param], #{ oneliner => "Local IP addresses" + , doc => "@doc-ifaddr" , type => emqttb:ifaddr_list() , default => [] , cli_operand => "ifaddr" diff --git a/src/scenarios/emqttb_scenario_persistent_session.erl b/src/scenarios/emqttb_scenario_persistent_session.erl index db13bd9..88a5305 100644 --- a/src/scenarios/emqttb_scenario_persistent_session.erl +++ b/src/scenarios/emqttb_scenario_persistent_session.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -75,7 +75,13 @@ model() -> #{ pub => #{ qos => {[value, cli_param], - #{ type => emqttb:qos() + #{ onliner => "QoS of published messages" + , doc => "@quotation warning + Changing QoS to values other then 2 is likely to cause consume stage to hang, + since it has to consume the exact number of messages as previously produced. + @end quotation + " + , type => emqttb:qos() , default => 2 , cli_operand => "pub-qos" }} @@ -127,6 +133,11 @@ model() -> #{ qos => {[value, cli_param], #{ oneliner => "Subscription QoS" + , doc => "@quotation warning + Changing QoS to values other then 2 is likely to cause consume stage to hang, + since it has to consume the exact number of messages as previously produced. + @end quotation + " , type => emqttb:qos() , default => 2 , cli_operand => "sub-qos" diff --git a/src/scenarios/emqttb_scenario_pub.erl b/src/scenarios/emqttb_scenario_pub.erl index 170cbf5..7c2225f 100644 --- a/src/scenarios/emqttb_scenario_pub.erl +++ b/src/scenarios/emqttb_scenario_pub.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%%Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%%Copyright (c) 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -47,7 +47,9 @@ model() -> #{ topic => {[value, cli_param], - #{ type => binary() + #{ onliner => "Publish to topic" + , doc => "@xref{Topic Patterns}\n" + , type => binary() , cli_operand => "topic" , cli_short => $t }} diff --git a/src/scenarios/emqttb_scenario_sub.erl b/src/scenarios/emqttb_scenario_sub.erl index 8a7e5ca..951c40e 100644 --- a/src/scenarios/emqttb_scenario_sub.erl +++ b/src/scenarios/emqttb_scenario_sub.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%%Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%%Copyright (c) 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -47,6 +47,7 @@ model() -> #{ topic => {[value, cli_param], #{ oneliner => "Topic that the clients shall subscribe" + , doc => "@xref{Topic Patterns}\n" , type => binary() , cli_operand => "topic" , cli_short => $t @@ -79,6 +80,7 @@ model() -> , expiry => {[value, cli_param], #{ oneliner => "Set 'Session-Expiry' for persistent sessions (seconds)" + , doc => "See @url{https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901048,Session Expiry Interval}.\n" , type => union(non_neg_integer(), undefined) , default => undefined , cli_operand => "expiry" @@ -94,19 +96,39 @@ model() -> }} , parse_metadata => {[value, cli_param], - #{ type => boolean() + #{ oneliner => "Extract metadata from message payloads" + , doc => "Subscribers will report end-to-end latency when this option is enabled. + + @quotation Warning + Publishers should insert metadata into the payloads. + For example, when using @ref{value/scenarios/pub,pub scenario} it's necessary to enable @ref{value/scenarios/pub/_/metadata,metadata} generation. + @end quotation + + @quotation Warning + In order to measure latency accurately, the scenario should ensure that publishers reside on the same emqttb host with the subscribers. + Otherwise clock skew between different load generator instances will introduce a systematic error. + @end quotation + + " + , type => boolean() , default => false , cli_operand => "parse-metadata" }} , verify_sequence => {[value, cli_param], - #{ type => boolean() + #{ oneliner => "Verify sequence of messages" + , doc => "@xref{Verify Message Sequence}. Implies @ref{value/scenarios/sub/_/parse_metadata,parse_metadata}.\n" + , type => boolean() , default => false , cli_operand => "verify-sequence" }} , clean_start => {[value, cli_param], - #{ type => boolean() + #{ oneliner => "Clean Start" + , doc => "Note: in order to disable clean start (and make the session persistent) this flag should be set to @code{false} + (for example, @code{emqttb @@sub +c ...} via CLI). + " + , type => boolean() , default => true , cli_operand => "clean-start" , cli_short => $c From 00f03448fbfa0d2978bdbfe916851bae3d6356cf Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Wed, 1 Jan 2025 22:32:30 +0100 Subject: [PATCH 3/7] fix: Document metrics and autorates --- bin/emqttb | 10 +++++-- doc/src/emqttb.texi | 8 +++++ doc/src/schema.texi | 20 ++++++------- rebar.config.script | 4 +-- scripts/docgen.escript | 49 +++++++++++++++++++++++-------- src/conf/emqttb_conf_model.erl | 2 +- src/framework/emqttb_autorate.erl | 17 +++++++---- src/metrics/emqttb_metrics.erl | 17 ++++++----- 8 files changed, 87 insertions(+), 40 deletions(-) diff --git a/bin/emqttb b/bin/emqttb index adf06cd..1cbd34d 100755 --- a/bin/emqttb +++ b/bin/emqttb @@ -10,12 +10,16 @@ ERTS_PATH=$RUNNER_ROOT_DIR/erts-$ERTS_VSN/bin ulimit -n $(ulimit -Hn) +help() { + info "${RUNNER_ROOT_DIR}/doc/info/emqttb.info" "$@" +} + if [ $# -eq 2 ] && [ $1 = "--help" ]; then - man -l "${RUNNER_ROOT_DIR}/doc/man/emqttb-${2}.1" + help "${2}" elif [ $# -eq 1 ] && [ $1 = "--help" ]; then - man -l "${RUNNER_ROOT_DIR}/doc/man/emqttb.1" + help elif [ $# -eq 0 ]; then - man -l "${RUNNER_ROOT_DIR}/doc/man/emqttb.1" + help else exec ${ERTS_PATH}/escript ${RUNNER_ESCRIPT_DIR}/emqttb "$@" fi diff --git a/doc/src/emqttb.texi b/doc/src/emqttb.texi index ee92e3e..29af426 100644 --- a/doc/src/emqttb.texi +++ b/doc/src/emqttb.texi @@ -52,6 +52,14 @@ Copyright @copyright{} 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. @chapter All Configurable Values @include value.texi +@node Autorate +@chapter List of autorate variables + @include autorate.texi + +@node Metrics +@chapter List of metrics + @include metric.texi + @node Index @unnumbered Index diff --git a/doc/src/schema.texi b/doc/src/schema.texi index 234493a..907f949 100644 --- a/doc/src/schema.texi +++ b/doc/src/schema.texi @@ -77,16 +77,16 @@ Supported units: @table @samp - @item us - microseconds - @item ms - milliseconds - @item s - seconds - @item min - minutes - @item h - hours + @item us + microseconds + @item ms + milliseconds + @item s + seconds + @item min + minutes + @item h + hours @end table If unit is not specified then @samp{ms} is assumed. diff --git a/rebar.config.script b/rebar.config.script index aafd36c..f29f1e7 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -34,8 +34,8 @@ CopyDocsSteps = case CanBuildDocs() of true -> [ {mkdir, "doc"} - , {copy, "_build/lee_doc/html/", "doc/html"} - , {copy, "_build/lee_doc/man/", "doc/man"} + , {copy, "doc/html/", "doc/html"} + , {copy, "doc/info/", "doc/info"} ]; false -> [] diff --git a/scripts/docgen.escript b/scripts/docgen.escript index 535cdba..22a43d6 100755 --- a/scripts/docgen.escript +++ b/scripts/docgen.escript @@ -2,22 +2,47 @@ %%! -pa _build/default/lib/lee/ebin -pa _build/default/lib/typerefl/ebin -pa _build/default/lib/emqttb/ebin -pa ./_build/default/checkouts/lee/ebin %% -*- erlang -*- +-include_lib("lee/include/lee.hrl"). + main(OutDir) -> ExtractorConfig = #{ output_dir => OutDir , extension => ".texi" - , formatter => fun lee_doc:texinfo/3 - , metatypes => [cli_param, value, os_env] + , formatter => fun texinfo/3 + , metatypes => [cli_param, value, os_env, autorate, metric] }, ok = emqttb_conf:load_model(), [_|_] = lee_doc:make_docs(emqttb_conf:model(), ExtractorConfig). -%% main(OutFile) -> -%% AsciidocOptions = #{}, -%% ok = filelib:ensure_dir(OutFile), -%% io:format(user, "Enriching model...~n", []), -%% RawModel = lee_asciidoc:enrich_model(AsciidocOptions, emqttb_conf_model:model()), -%% io:format(user, "Compiling model...~n", []), -%% {ok, Model} = emqttb_conf:compile_model(RawModel), -%% io:format(user, "Dumping XML...~n", []), -%% ok = lee_doc:make_docs(Model, #{output_file => OutFile}), -%% io:format(user, "Done, exiting.~n", []). +texinfo(Options, FD, L) when is_list(L) -> + [texinfo(Options, FD, I) || I <- L], + ok; +texinfo(Options, FD, Doclet) -> + P = fun(L) -> io:put_chars(FD, L) end, + case Doclet of + %% Autorate + #doclet{mt = autorate, tag = container, data = Children} -> + P(["@table @code\n"]), + texinfo(Options, FD, Children), + P(["@end table\n"]); + #doclet{mt = autorate, tag = autorate, key = Key, data = Title} -> + P(["@anchor{", lee_doc:texi_key([autorate | Key]), "}\n"]), + P(["@item ", Title, $\n]); + %% Metric + #doclet{mt = metric, tag = container, data = Children} -> + P(["@itemize\n"]), + texinfo(Options, FD, Children), + P(["@end itemize\n"]); + #doclet{mt = metric, tag = metric, key = Key} -> + P(["@item\n@anchor{", lee_doc:texi_key([metric | Key]), "}\n"]), + P(["@verbatim\n", lee_lib:term_to_string(Key), "\n@end verbatim\n"]); + #doclet{mt = metric, tag = type, data = Data} -> + P(["@b{Type}: ", Data, "\n\n"]); + #doclet{mt = metric, tag = labels, data = Data} -> + P("@b{Prometheus labels}: "), + P(lists:join(", ", Data)), + P("\n\n"); + #doclet{mt = metric, tag = prometheus_id, data = Data} -> + P(["@b{Prometheus name}: @code{", Data, "}\n\n"]); + _ -> + lee_doc:texinfo(Options, FD, Doclet) + end. diff --git a/src/conf/emqttb_conf_model.erl b/src/conf/emqttb_conf_model.erl index 848392a..e6a285a 100644 --- a/src/conf/emqttb_conf_model.erl +++ b/src/conf/emqttb_conf_model.erl @@ -89,7 +89,7 @@ model() -> , cli_operand => "rest-listen" }} , tls => - {[value, os_env, undocumented], % TODO + {[value, os_env], #{ oneliner => "Enable TLS for REST listener" , type => boolean() , default => false diff --git a/src/framework/emqttb_autorate.erl b/src/framework/emqttb_autorate.erl index 5fadfe8..ecaeb09 100644 --- a/src/framework/emqttb_autorate.erl +++ b/src/framework/emqttb_autorate.erl @@ -29,7 +29,7 @@ %% gen_server callbacks: -export([init/1, handle_call/3, handle_cast/2, handle_info/2]). %% lee_metatype callbacks: --export([names/1, metaparams/1, meta_validate/2, validate_node/5, description/3]). +-export([names/1, metaparams/1, meta_validate/2, validate_node/5, description/3, doc_refer/4]). %% internal exports: -export([start_link/1, model/0, from_model/1]). @@ -313,18 +313,25 @@ from_model(Key) -> #mnode{metaparams = #{autorate_id := Id}} = lee_model:get(Key, ?MYMODEL), Id. -description(autorate = MT, Model, _Options) -> - Chapter = autorate, +description(autorate = MT, Model, Options) -> Content = [begin #mnode{metaparams = Attrs} = lee_model:get(Key, Model), Title = atom_to_list(?m_attr(autorate, autorate_id, Attrs)), - lee_doc:refer_value(Model, Chapter, Key, Title) + [ #doclet{mt = autorate, tag = autorate, key = Key, data = Title} + , lee_doc:get_oneliner(autorate, Model, Key) + , lee_metatype:doc_refer(value, Model, Options, Key) + ] end || Key <- lee_model:get_metatype_index(MT, Model) ], - lee_doc:chapter(MT, "Automatically adjusted values", Content); + [#doclet{mt = autorate, tag = container, data = Content}]; description(_, _, _) -> []. +doc_refer(autorate, _Model, _Options, Key) -> + [#doclet{mt = autorate, tag = see_also, data = #doc_xref{mt = autorate, key = Key}}]; +doc_refer(_, _Model, _Options, _Key) -> + []. + %%================================================================================ %% gen_server callbacks %%================================================================================ diff --git a/src/metrics/emqttb_metrics.erl b/src/metrics/emqttb_metrics.erl index 7a429a0..fff3173 100644 --- a/src/metrics/emqttb_metrics.erl +++ b/src/metrics/emqttb_metrics.erl @@ -220,16 +220,19 @@ validate_node(metric, _, _, _, _) -> {[], []}. description(metric = MT, Model, _Options) -> - Chapter = metrics, Content = [begin #mnode{metaparams = MPs} = lee_model:get(Key, Model), - Oneliner = ?m_attr(metric, oneliner, MPs, ""), - {refsection, [{'xml:id', lee_doc:format_key(Chapter, Key)}], - [ {title, [lee_lib:term_to_string(Key)]} - , {para, [Oneliner]} - ]} + Labels = ?m_attr(metric, labels, MPs), + Type = ?m_attr(metric, metric_type, MPs), + {PromId, _} = ?m_attr(metric, id, MPs), + [ #doclet{mt = metric, tag = metric, key = Key} + , #doclet{mt = metric, tag = type, data = atom_to_binary(Type)} + , #doclet{mt = metric, tag = prometheus_id, data = atom_to_binary(PromId)} + , #doclet{mt = metric, tag = labels, data = lists:map(fun atom_to_binary/1, Labels)} + , lee_doc:get_oneliner(metric, Model, Key) + ] end || Key <- lee_model:get_metatype_index(MT, Model)], - lee_doc:chapter(Chapter, "Metrics", Content); + [#doclet{mt = metric, tag = container, data = Content}]; description(_, _, _) -> []. From d073d6b932d1f7d8bc8130f8ff1f0e2438fd1cbc Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Wed, 1 Jan 2025 23:00:00 +0100 Subject: [PATCH 4/7] ci: Build docs by texinfo --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 4 ++-- Makefile | 7 ++----- bin/emqttb | 8 +++++++- rebar.config | 2 +- scripts/docgen.escript | 2 +- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7a1fc3..318ff7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Install additional packages run: | apt-get update - apt-get install -y openjdk-11-jdk asciidoctor xsltproc docbook-xsl + apt-get install -y texinfo - name: Compile and run tests env: BUILD_WITHOUT_QUIC: "true" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ec0804..709a7aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: name: Install additional packages run: | apt-get update - apt-get install -y openjdk-11-jdk asciidoctor xsltproc docbook-xsl python3-pygments + apt-get install -y texinfo - name: Build shell: bash @@ -41,7 +41,7 @@ jobs: export BUILD_WITHOUT_QUIC=1 git config --global --add safe.directory $(pwd) make release - tar czf docs.tar.gz _build/lee_doc/html _build/lee_doc/man _build/lee_doc/src/ + tar czf docs.tar.gz doc/html doc/info - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: diff --git a/Makefile b/Makefile index bfb5436..7ea9300 100644 --- a/Makefile +++ b/Makefile @@ -33,9 +33,6 @@ release: compile docs @$(REBAR) as emqttb tar @$(CURDIR)/scripts/rename-package.sh -$(DOCBOOK): scripts/docgen.escript compile - escript scripts/docgen.escript $@ - .PHONY: docs docs: doc/info/emqttb.info doc/html/index.html @@ -46,8 +43,8 @@ doc/html/index.html: $(TEXINFO) # -c MATHJAX_CONFIGURATION="$(MATHJAX_OPTS)" texi2any -I doc/lee --html -c INFO_JS_DIR=js -c HTML_MATH=mathjax -o doc/html/ $< -$(TEXINFO): compile - ./scripts/docgen.escript doc/lee +$(TEXINFO): scripts/docgen.escript compile + $(CURDIR)/scripts/docgen.escript doc/lee .PHONY: clean clean: distclean diff --git a/bin/emqttb b/bin/emqttb index 1cbd34d..53c3f08 100755 --- a/bin/emqttb +++ b/bin/emqttb @@ -11,7 +11,13 @@ ERTS_PATH=$RUNNER_ROOT_DIR/erts-$ERTS_VSN/bin ulimit -n $(ulimit -Hn) help() { - info "${RUNNER_ROOT_DIR}/doc/info/emqttb.info" "$@" + local file + file="${RUNNER_ROOT_DIR}/doc/info/emqttb.info" + if command -v emacs; then + emacs -eval "(info \"${file}\")" + else + info "${file}" "${@}" + fi } if [ $# -eq 2 ] && [ $1 = "--help" ]; then diff --git a/rebar.config b/rebar.config index 9d28340..2e1a63d 100644 --- a/rebar.config +++ b/rebar.config @@ -5,7 +5,7 @@ {deps, [ {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.13.5"}}} , {gproc, "0.9.1"} - , {lee, {git, "https://github.com/k32/lee", {tag, "0.5.0"}}} + , {lee, {git, "https://github.com/k32/lee", {tag, "0.5.1"}}} , {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe", {tag, "1.0.10"}}} , {prometheus, {git, "https://github.com/deadtrickster/prometheus.erl", {tag, "v4.8.2"}}} , hackney diff --git a/scripts/docgen.escript b/scripts/docgen.escript index 22a43d6..e4992e4 100755 --- a/scripts/docgen.escript +++ b/scripts/docgen.escript @@ -1,5 +1,5 @@ #!/usr/bin/escript -%%! -pa _build/default/lib/lee/ebin -pa _build/default/lib/typerefl/ebin -pa _build/default/lib/emqttb/ebin -pa ./_build/default/checkouts/lee/ebin +%%! -pa _build/default/lib/lee/ebin -pa _build/default/lib/typerefl/ebin -pa _build/default/lib/emqttb/ebin %% -*- erlang -*- -include_lib("lee/include/lee.hrl"). From 3e5141ad90c0c35e42d556cfbaf3c5e148c1112f Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Wed, 1 Jan 2025 23:41:42 +0100 Subject: [PATCH 5/7] ci: Fix escript path --- Makefile | 2 +- scripts/docgen.escript | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7ea9300..0b13554 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ doc/html/index.html: $(TEXINFO) texi2any -I doc/lee --html -c INFO_JS_DIR=js -c HTML_MATH=mathjax -o doc/html/ $< $(TEXINFO): scripts/docgen.escript compile - $(CURDIR)/scripts/docgen.escript doc/lee + scripts/docgen.escript doc/lee .PHONY: clean clean: distclean diff --git a/scripts/docgen.escript b/scripts/docgen.escript index e4992e4..3238522 100755 --- a/scripts/docgen.escript +++ b/scripts/docgen.escript @@ -1,7 +1,9 @@ -#!/usr/bin/escript +#!/usr/bin/env escript %%! -pa _build/default/lib/lee/ebin -pa _build/default/lib/typerefl/ebin -pa _build/default/lib/emqttb/ebin %% -*- erlang -*- +-mode(compile). + -include_lib("lee/include/lee.hrl"). main(OutDir) -> From d8a5bde6718b5feeb3ae21bdb811862235195917 Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:53:22 +0100 Subject: [PATCH 6/7] docs: Restructure autorate documentation --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- Makefile | 1 + bin/emqttb | 12 ++---- doc/src/autorate_descr.texi | 46 ++++++++++++++++++++++ doc/src/emqttb.texi | 64 +++++++++++++++++++++---------- doc/src/intro.texi | 14 ++++++- doc/src/schema.texi | 43 --------------------- src/conf/emqttb_conf_model.erl | 2 +- src/framework/emqttb_autorate.erl | 6 +-- 10 files changed, 113 insertions(+), 79 deletions(-) create mode 100644 doc/src/autorate_descr.texi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 318ff7c..9d10891 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Install additional packages run: | apt-get update - apt-get install -y texinfo + apt-get install -y texinfo install-info - name: Compile and run tests env: BUILD_WITHOUT_QUIC: "true" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 709a7aa..7b0e90a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: name: Install additional packages run: | apt-get update - apt-get install -y texinfo + apt-get install -y texinfo install-info - name: Build shell: bash diff --git a/Makefile b/Makefile index 0b13554..8924aad 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ docs: doc/info/emqttb.info doc/html/index.html doc/info/emqttb.info: $(TEXINFO) texi2any -I doc/lee --info -o $@ $< + install-info $@ doc/info/dir doc/html/index.html: $(TEXINFO) # -c MATHJAX_CONFIGURATION="$(MATHJAX_OPTS)" diff --git a/bin/emqttb b/bin/emqttb index 53c3f08..0758c3e 100755 --- a/bin/emqttb +++ b/bin/emqttb @@ -6,18 +6,14 @@ RUNNER_ROOT_DIR="{{ runner_root_dir }}" RUNNER_ESCRIPT_DIR="{{ runner_escript_dir }}" ERTS_VSN="{{ erts_vsn }}" -ERTS_PATH=$RUNNER_ROOT_DIR/erts-$ERTS_VSN/bin +ERTS_PATH="${RUNNER_ROOT_DIR}/erts-${ERTS_VSN}/bin" + +export INFOPATH="${INFOPATH}:${RUNNER_ROOT_DIR}/doc/info" ulimit -n $(ulimit -Hn) help() { - local file - file="${RUNNER_ROOT_DIR}/doc/info/emqttb.info" - if command -v emacs; then - emacs -eval "(info \"${file}\")" - else - info "${file}" "${@}" - fi + info emqttb "${@}" } if [ $# -eq 2 ] && [ $1 = "--help" ]; then diff --git a/doc/src/autorate_descr.texi b/doc/src/autorate_descr.texi new file mode 100644 index 0000000..7925196 --- /dev/null +++ b/doc/src/autorate_descr.texi @@ -0,0 +1,46 @@ +When the loadgen creates too much traffic, the system may get overloaded. +In this case, the test usually has to be restarted all over again with different parameters. +This can be very expensive in man-hours and computing resources. + +In order to prevent that, emqttb can tune some parameters (such as message publishing interval) +automatically using +@url{https://controlguru.com/integral-reset-windup-jacketing-logic-and-the-velocity-pi-form/,PI controller}. + +The following formula is used for the error function: + +@math{e=(a_{SP} - a_{PV}) a_{coeff}} + +@node Autoscale +@section Autoscale + +A special autorate controlling the rate of spawning new clients is implicitly created for each client group. +Its name usually follows the pattern @code{%scenario%/conn_interval}. + +By default, the number of pending (unacked) connections is used as the process variable. +Number of pending connections is a metric that responds very fast to target overload, so it makes a reasonable default. + +For example the following command can automatically adjust the rate of connections: + +@example +./emqttb --pushgw @@conn -I 10ms -N 5_000 \ + @@a -a conn/conninterval -V 1000 --setpoint 10 +@end example + +@node SCRAM +@section SCRAM + +Normally, autorate adjusts the control variable gradually. +However, sometimes the system under test becomes overloaded suddenly, and in this case slowly decreasing the pressure may not be efficient enough. +To combat this situation, @code{emqttb} has "SCRAM" mechanism, that immediately resets the control variable to a @ref{value/autorate/_/scram/override,configured safe value}. +This happens when the value of process variable exceeds a @ref{value/autorate/_/scram/threshold,certain threshold}. + +SCRAM mode remains in effect until the number of pending connections becomes less than @math{\frac{t h}{100}} +where @math{t} is threshold, and @math{h} is @ref{value/autorate/_/scram/hysteresis,hystersis}. + +@node Autorate List +@section List of autorate variables +@include autorate.texi + +@node Metrics List +@section List of metrics +@include metric.texi diff --git a/doc/src/emqttb.texi b/doc/src/emqttb.texi index 29af426..d31a5b8 100644 --- a/doc/src/emqttb.texi +++ b/doc/src/emqttb.texi @@ -3,6 +3,12 @@ @setfilename emqttb.info @settitle EMQTTB @c %**end of header + +@dircategory EMQTTB: an MQTT load generator. +@direntry +* EMQTTB: (emqttb). +@end direntry + @copying A scriptable autotuning MQTT load generator. @@ -32,33 +38,51 @@ Copyright @copyright{} 2022-2025 EMQ Technologies Co., Ltd. All Rights Reserved. @insertcopying @end ifnottex -@include intro.texi +@node Introduction +@chapter Introduction + @include intro.texi @node Invokation @chapter Invokation - @node CLI - @section CLI Arguments - @lowersections - @include cli_param.texi - @raisesections - - @node OS Environment Variables - @section OS Environment Variables - @lowersections - @include os_env.texi - @raisesections +EMQTTB executable accepts named CLI arguments (such as @option{--foo} or -f positional arguments and actions. Actions are special CLI arguments that start with @@ character (e.g. @option{@@pub}). Actions correspond to different load-generation scenarios, such as @option{@@pub} and @option{@@sub}, client group configurations (@option{@@g}) and autorates (@option{@@g}). -@node All Values -@chapter All Configurable Values - @include value.texi +Each CLI action defines its own scope of named and positional CLI arguments. Positional arguments are always specified after named arguments within the scope. There is also a global scope of arguments that don’t belong to any action. Global arguments are specified before the very first action. + +Example: + +@example +emqttb --foo --bar 1 positional1 positional2 @@action1 --foo 1 positional1 @@action2 ... + |___________| |_____________________| |_________________| + |regular args positional args | action1 scope + |___________________________________| + global scope +@end example + +Flag negation: + +Boolean flag arguments can be set to false by adding @code{no-} prefix, for example @option{--no-pushgw}. + +Short boolean flags can be set to false using @code{+} sigil instead of @code{-}. + +@node CLI +@section CLI Arguments +@lowersections + @include cli_param.texi +@raisesections + +@node OS Environment Variables +@section OS Environment Variables +@lowersections + @include os_env.texi +@raisesections @node Autorate -@chapter List of autorate variables - @include autorate.texi +@chapter Autorate + @include autorate_descr.texi -@node Metrics -@chapter List of metrics - @include metric.texi +@node All Values +@chapter All Configurable Values + @include value.texi @node Index @unnumbered Index diff --git a/doc/src/intro.texi b/doc/src/intro.texi index c7da801..5b7b269 100644 --- a/doc/src/intro.texi +++ b/doc/src/intro.texi @@ -1,5 +1,15 @@ -@node Introduction -@chapter Introduction +@node Concepts +@section Concepts: Worker, Group, Scenario... + +@b{Worker} is a process that corresponds to a single MQTT client. + +@b{Behavior} is a callback module containing functions that workers run in a loop. + +@b{Group} is a supervised colletion of workers running the same behavior and sharing the same configuration. Group manager controls the number of workers, restarts failed workers and implements ramp up/down logic. + +@b{Scenario} is a callback module that creates several worker groups and manupulates group configuration using autorate. + +@b{Autorate} a process that adjusts group parameters (such as number of workers in the group, or worker configuration) based on constants or dynamic parameters, e.g. available RAM or CPU load, observed latency and so on. @node Topic Patterns @section Topic Patterns diff --git a/doc/src/schema.texi b/doc/src/schema.texi index 907f949..f8853a0 100644 --- a/doc/src/schema.texi +++ b/doc/src/schema.texi @@ -30,49 +30,6 @@ @end macro -@macro doc-autorate - When the loadgen creates too much traffic, the system may get overloaded. - In this case, the test usually has to be restarted all over again with different parameters. - This can be very expensive in man-hours and computing resources. - - In order to prevent that, emqttb can tune some parameters (such as message publishing interval) - automatically using - @url{https://controlguru.com/integral-reset-windup-jacketing-logic-and-the-velocity-pi-form/,PI controller}. - - The following formula is used for the error function: - - @math{e=(a_{SP} - a_{PV}) a_{coeff}} - - @heading Autoscale - @cindex autoscale - - A special autorate controlling the rate of spawning new clients is implicitly created for each client group. - Its name usually follows the pattern @code{%scenario%/conn_interval}. - - - By default, the number of pending (unacked) connections is used as the process variable. - Number of pending connections is a metric that responds very fast to target overload, so it makes a reasonable default. - - For example the following command can automatically adjust the rate of connections: - - @example - ./emqttb --pushgw @@conn -I 10ms -N 5_000 \ - @@a -a conn/conninterval -V 1000 --setpoint 10 - @end example - -@end macro - -@macro doc-scram - Normally, autorate adjusts the control variable gradually. - However, sometimes the system under test becomes overloaded suddenly, and in this case slowly decreasing the pressure may not be efficient enough. - To combat this situation, @code{emqttb} has "SCRAM" mechanism, that immediately resets the control variable to a @ref{value/autorate/_/scram/override,configured safe value}. - This happens when the value of process variable exceeds a @ref{value/autorate/_/scram/threshold,certain threshold}. - - SCRAM mode remains in effect until the number of pending connections becomes less than @math{\frac{t h}{100}} - where @math{t} is threshold, and FIXME @math{h} is @ref{value/autorate/_/scram/hysteresis,hystersis}. - -@end macro - @macro doc-interval Supported units: diff --git a/src/conf/emqttb_conf_model.erl b/src/conf/emqttb_conf_model.erl index e6a285a..8ff963a 100644 --- a/src/conf/emqttb_conf_model.erl +++ b/src/conf/emqttb_conf_model.erl @@ -282,7 +282,7 @@ model() -> , autorate => {[map, cli_action], #{ oneliner => "Autorate configuration" - , doc => "@doc-autorate" + , doc => "@xref{Autorate}\n" , cli_operand => "a" , key_elements => [[id]] }, diff --git a/src/framework/emqttb_autorate.erl b/src/framework/emqttb_autorate.erl index ecaeb09..05a046c 100644 --- a/src/framework/emqttb_autorate.erl +++ b/src/framework/emqttb_autorate.erl @@ -131,7 +131,7 @@ model() -> #{ oneliner => "ID of the autorate configuration" , doc => "Autorate identifier. This value must be equal to one of the elements returned by @code{emqttb @@ls autorate} command. - Full list is also available in FIXME <> + Full list is also available in @ref{Autorate List}. " , type => atom() , default => default @@ -144,7 +144,7 @@ model() -> , doc => "This parameter specifies ID of the metric that senses pressure on the SUT and serves as the process variable (PV). Its value must be equal to one of the metric IDs returned by @code{emqttb @@ls metric} command. - Full list can be also found in FIXME <>. + Full list can be also found in @ref{Metrics List}. " , type => lee:model_key() , cli_operand => "pvar" @@ -223,7 +223,7 @@ model() -> #{ enabled => {[value, cli_param], #{ oneliner => "Enable SCRAM" - , doc => "@doc-scram" + , doc => "@xref{SCRAM}\n" , type => boolean() , default => false , cli_operand => "olp" From 98ca65dfbd09840a24faef336732338814a3c0f0 Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:33:13 +0100 Subject: [PATCH 7/7] fix: Apply remarks --- Makefile | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Makefile b/Makefile index 8924aad..2ac5dc3 100644 --- a/Makefile +++ b/Makefile @@ -2,16 +2,6 @@ REBAR ?= $(CURDIR)/rebar3 REBAR_URL ?= https://s3.amazonaws.com/rebar3/rebar3 TEXINFO := doc/src/emqttb.texi doc/lee/cli_params.texi doc/lee/os_env.texi doc/lee/value.texi -define MATHJAX_OPTS -loader: {\ - load: ['[tex]/physics'],\ - versionWarnings: false\ -},\ -tex: {\ - packages: {'[+]': ['physics']}\ -} -endef - .PHONY: all all: $(REBAR) $(REBAR) do compile, dialyzer, xref, eunit, ct @@ -41,7 +31,6 @@ doc/info/emqttb.info: $(TEXINFO) install-info $@ doc/info/dir doc/html/index.html: $(TEXINFO) -# -c MATHJAX_CONFIGURATION="$(MATHJAX_OPTS)" texi2any -I doc/lee --html -c INFO_JS_DIR=js -c HTML_MATH=mathjax -o doc/html/ $< $(TEXINFO): scripts/docgen.escript compile