]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
DOC: otel: test: added test README-* files
authorMiroslav Zagorac <mzagorac@haproxy.com>
Sat, 21 Mar 2026 17:39:52 +0000 (18:39 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 13 Apr 2026 07:23:26 +0000 (09:23 +0200)
Added README documentation for each test configuration (sa, cmp, ctx,
fe-be, empty, full) describing event coverage, signal usage, instrument
tables, span hierarchies and run instructions.

addons/otel/test/README-cmp [new file with mode: 0644]
addons/otel/test/README-ctx [new file with mode: 0644]
addons/otel/test/README-empty [new file with mode: 0644]
addons/otel/test/README-fe-be [new file with mode: 0644]
addons/otel/test/README-full [new file with mode: 0644]
addons/otel/test/README-sa [new file with mode: 0644]

diff --git a/addons/otel/test/README-cmp b/addons/otel/test/README-cmp
new file mode 100644 (file)
index 0000000..ab1b6d7
--- /dev/null
@@ -0,0 +1,85 @@
+Comparison test configuration (cmp/)
+====================================
+
+The 'cmp' test is a simplified standalone configuration made for comparison with
+other tracing implementations.  It uses a reduced set of events and a compact
+span hierarchy without context propagation, groups or metrics.  This
+configuration is closer to a typical production deployment.
+
+All response-side scopes (http_response, http_response-error, server_session_end
+and client_session_end) share the on-http-response event, which means they fire
+in a single batch at response time.
+
+
+Files
+-----
+
+  cmp/otel.cfg    OTel filter configuration (scopes)
+  cmp/haproxy.cfg HAProxy frontend/backend configuration
+  cmp/otel.yml    Exporter, processor, reader and provider definitions
+  run-cmp.sh      Convenience script to launch HAProxy with this config
+
+
+Events
+------
+
+  T = Trace (span)
+
+  This configuration produces traces only -- no metrics or log-records.
+
+  Request analyzer events:
+
+    Event                      Scope                    T
+    --------------------------------------------------------
+    on-client-session-start    client_session_start     x
+    on-frontend-tcp-request    frontend_tcp_request     x
+    on-frontend-http-request   frontend_http_request    x
+    on-backend-tcp-request     backend_tcp_request      x
+    on-backend-http-request    backend_http_request     x
+    on-server-unavailable      server_unavailable       x
+
+  Response analyzer events:
+
+    Event                      Scope                    T
+    --------------------------------------------------------
+    on-server-session-start    server_session_start     x
+    on-tcp-response            tcp_response             x
+    on-http-response           http_response            x
+    on-http-response           http_response-error      x  (conditional)
+    on-http-response           server_session_end       -  (finish only)
+    on-http-response           client_session_end       -  (finish only)
+
+  The http_response-error scope fires conditionally when the ACL
+  acl-http-status-ok (status 100:399) does not match, setting an error status
+  on the "HTTP response" span.
+
+  The server_session_end and client_session_end scopes are bound to the
+  on-http-response event and only perform finish operations.
+
+
+Span hierarchy
+--------------
+
+    "HAProxy session" (root)
+     +-- "Client session"
+          +-- "Frontend TCP request"
+               +-- "Frontend HTTP request"
+                    +-- "Backend TCP request"
+                         +-- "Backend HTTP request"
+
+    "HAProxy session" (root)
+     +-- "Server session"
+          +-- "TCP response"
+               +-- "HTTP response"
+
+
+Running the test
+----------------
+
+From the test/ directory:
+
+  % ./run-cmp.sh [/path/to/haproxy] [pidfile]
+
+If no arguments are given, the script looks for the haproxy binary three
+directories up from the current working directory.  The backend origin server
+must be running on 127.0.0.1:8000.
diff --git a/addons/otel/test/README-ctx b/addons/otel/test/README-ctx
new file mode 100644 (file)
index 0000000..512706e
--- /dev/null
@@ -0,0 +1,149 @@
+Context propagation test configuration (ctx/)
+=============================================
+
+The 'ctx' test is a standalone configuration that uses inject/extract context
+propagation on every scope.  Spans are opened using extracted span contexts
+stored in HAProxy variables as parent references instead of direct span names.
+This adds the overhead of context serialization, variable storage and
+deserialization on every scope execution.
+
+The event coverage matches the 'sa' configuration.  The key difference is the
+propagation mechanism: each scope injects its context into a numbered variable
+(otel_ctx_1 through otel_ctx_17) and the next scope extracts from that variable
+to establish the parent relationship.
+
+The client_session_start event is split into two scopes (client_session_start_1
+and client_session_start_2) to demonstrate inject/extract between scopes
+handling the same event.
+
+
+Files
+-----
+
+  ctx/otel.cfg    OTel filter configuration (scopes, groups, contexts)
+  ctx/haproxy.cfg HAProxy frontend/backend configuration
+  ctx/otel.yml    Exporter, processor, reader and provider definitions
+  run-ctx.sh      Convenience script to launch HAProxy with this config
+
+
+Events
+------
+
+  T = Trace (span)
+
+  This configuration produces traces only -- no metrics or log-records.
+
+  Stream lifecycle events:
+
+    Event                    Scope                        T
+    ----------------------------------------------------------
+    on-client-session-start  client_session_start_1       x
+    on-client-session-start  client_session_start_2       x
+
+  Request analyzer events:
+
+    Event                              Scope                          T
+    --------------------------------------------------------------------
+    on-frontend-tcp-request            frontend_tcp_request           x
+    on-http-wait-request               http_wait_request              x
+    on-http-body-request               http_body_request              x
+    on-frontend-http-request           frontend_http_request          x
+    on-switching-rules-request         switching_rules_request        x
+    on-backend-tcp-request             backend_tcp_request            x
+    on-backend-http-request            backend_http_request           x
+    on-process-server-rules-request    process_server_rules_request   x
+    on-http-process-request            http_process_request           x
+    on-tcp-rdp-cookie-request          tcp_rdp_cookie_request         x
+    on-process-sticking-rules-request  process_sticking_rules_request x
+    on-client-session-end              client_session_end             -
+    on-server-unavailable              server_unavailable             -
+
+  Response analyzer events:
+
+    Event                              Scope                          T
+    --------------------------------------------------------------------
+    on-server-session-start            server_session_start           x
+    on-tcp-response                    tcp_response                   x
+    on-http-wait-response              http_wait_response             x
+    on-process-store-rules-response    process_store_rules_response   x
+    on-http-response                   http_response                  x
+    on-http-response                   http_response-error            x  (conditional)
+    on-server-session-end              server_session_end             -
+
+  The http_response_group (http_response_1, http_response_2) and
+  http_after_response_group (http_after_response) are invoked via http-response
+  and http-after-response directives in haproxy.cfg.
+
+
+Context propagation chain
+-------------------------
+
+  Each scope injects its span context into a HAProxy variable and the next scope
+  extracts it.  The variable names and their flow:
+
+    otel_ctx_1   "HAProxy session"              -> client_session_start_2
+    otel_ctx_2   "Client session"               -> frontend_tcp_request
+    otel_ctx_3   "Frontend TCP request"         -> http_wait_request
+    otel_ctx_4   "HTTP wait request"            -> http_body_request
+    otel_ctx_5   "HTTP body request"            -> frontend_http_request
+    otel_ctx_6   "Frontend HTTP request"        -> switching_rules_request
+    otel_ctx_7   "Switching rules request"      -> backend_tcp_request
+    otel_ctx_8   "Backend TCP request"          -> backend_http_request
+    otel_ctx_9   "Backend HTTP request"         -> process_server_rules_request
+    otel_ctx_10  "Process server rules request" -> http_process_request
+    otel_ctx_11  "HTTP process request"         -> tcp_rdp_cookie_request
+    otel_ctx_12  "TCP RDP cookie request"       -> process_sticking_rules_request
+    otel_ctx_13  "Process sticking rules req."  -> server_session_start
+    otel_ctx_14  "Server session"               -> tcp_response
+    otel_ctx_15  "TCP response"                 -> http_wait_response
+    otel_ctx_16  "HTTP wait response"           -> process_store_rules_response
+    otel_ctx_17  "Process store rules response" -> http_response
+
+  All contexts use both use-headers and use-vars injection modes, except
+  otel_ctx_14 and otel_ctx_15 which use use-vars only.
+
+
+Span hierarchy
+--------------
+
+  The span hierarchy is identical to the 'sa' configuration, but parent
+  relationships are established through extracted contexts rather than direct
+  span name references.
+
+  Request path:
+
+    "HAProxy session" (root)                                   [otel_ctx_1]
+     +-- "Client session"                                      [otel_ctx_2]
+          +-- "Frontend TCP request"                           [otel_ctx_3]
+               +-- "HTTP wait request"                         [otel_ctx_4]
+                    +-- "HTTP body request"                    [otel_ctx_5]
+                         +-- "Frontend HTTP request"           [otel_ctx_6]
+                              +-- "Switching rules request"    [otel_ctx_7]
+                                   +-- "Backend TCP request"   [otel_ctx_8]
+                                        +-- (continues to process_sticking_rules_request)
+
+  Response path:
+
+    "HAProxy session"                                          [otel_ctx_1]
+     +-- "Server session"                                      [otel_ctx_14]
+          +-- "TCP response"                                   [otel_ctx_15]
+               +-- "HTTP wait response"                        [otel_ctx_16]
+                    +-- "Process store rules response"         [otel_ctx_17]
+                         +-- "HTTP response"
+
+  Auxiliary spans:
+
+    "HAProxy session"
+     +-- "HAProxy response"       (http_after_response_group, on error)
+
+
+Running the test
+----------------
+
+From the test/ directory:
+
+  % ./run-ctx.sh [/path/to/haproxy] [pidfile]
+
+If no arguments are given, the script looks for the haproxy binary three
+directories up from the current working directory.  The backend origin server
+must be running on 127.0.0.1:8000.
diff --git a/addons/otel/test/README-empty b/addons/otel/test/README-empty
new file mode 100644 (file)
index 0000000..5f19651
--- /dev/null
@@ -0,0 +1,53 @@
+Empty test configuration (empty/)
+=================================
+
+The 'empty' test is a minimal configuration that loads the OTel filter without
+defining any scopes, events or groups.  The instrumentation block contains only
+the config directive pointing to the YAML pipeline definition.
+
+This configuration verifies that the filter initializes and shuts down cleanly
+when no telemetry is configured.  It exercises the full YAML parsing path
+(exporters, processors, readers, samplers, providers and signals) without
+producing any trace, metric or log-record data.
+
+
+Files
+-----
+
+  empty/otel.cfg    OTel filter configuration (instrumentation only)
+  empty/haproxy.cfg HAProxy frontend/backend configuration
+  empty/otel.yml    Exporter, processor, reader and provider definitions
+
+
+Events
+------
+
+  No events are registered.  The filter is loaded and attached to the frontend
+  but performs no per-stream processing.
+
+
+YAML pipeline
+-------------
+
+  Despite the empty filter configuration, the otel.yml file defines a complete
+  pipeline with all three signal types to verify that the YAML parser handles
+  the full configuration without errors:
+
+    Signal     Exporter                   Processor / Reader
+    -----------------------------------------------------------
+    traces     exporter_traces_otlp_http  processor_traces_batch
+    metrics    exporter_metrics_otlp_http reader_metrics
+    logs       exporter_logs_otlp_http    processor_logs_batch
+
+  Additional exporter definitions (otlp_file, otlp_grpc, ostream, memory,
+  zipkin, elasticsearch) are present in the YAML but are not wired into the
+  active signal pipelines.
+
+
+Running the test
+----------------
+
+There is no dedicated run script for the empty configuration.  To run it
+manually from the test/ directory:
+
+  % /path/to/haproxy -f haproxy-common.cfg -f empty/haproxy.cfg
diff --git a/addons/otel/test/README-fe-be b/addons/otel/test/README-fe-be
new file mode 100644 (file)
index 0000000..647a21c
--- /dev/null
@@ -0,0 +1,124 @@
+Frontend / backend test configuration (fe/ + be/)
+=================================================
+
+The 'fe-be' test uses two cascaded HAProxy instances to demonstrate
+inter-process trace context propagation via HTTP headers.  The frontend instance
+(fe/) creates the root trace and injects span context into the HTTP request
+headers.  The backend instance (be/) extracts that context and continues the
+trace as a child of the frontend's span.
+
+The two instances run as separate processes: the frontend listens on port 10080
+and proxies to the backend on port 11080, which in turn proxies to the origin
+server on port 8000.
+
+
+Files
+-----
+
+  fe/otel.cfg    OTel filter configuration for the frontend instance
+  fe/haproxy.cfg HAProxy configuration for the frontend instance
+  be/otel.cfg    OTel filter configuration for the backend instance
+  be/haproxy.cfg HAProxy configuration for the backend instance
+  run-fe-be.sh   Convenience script to launch both instances
+
+
+Events
+------
+
+  T = Trace (span)
+
+  Both instances produce traces only -- no metrics or log-records.
+
+  Frontend (fe/) events:
+
+    Event                      Scope                    T
+    --------------------------------------------------------
+    on-client-session-start    client_session_start     x
+    on-frontend-tcp-request    frontend_tcp_request     x
+    on-frontend-http-request   frontend_http_request    x
+    on-backend-tcp-request     backend_tcp_request      x
+    on-backend-http-request    backend_http_request     x
+    on-client-session-end      client_session_end       -
+    on-server-session-start    server_session_start     x
+    on-tcp-response            tcp_response             x
+    on-http-response           http_response            x
+    on-server-session-end      server_session_end       -
+
+  Backend (be/) events:
+
+    Event                      Scope                    T
+    --------------------------------------------------------
+    on-frontend-http-request   frontend_http_request    x
+    on-backend-tcp-request     backend_tcp_request      x
+    on-backend-http-request    backend_http_request     x
+    on-client-session-end      client_session_end       -
+    on-server-session-start    server_session_start     x
+    on-tcp-response            tcp_response             x
+    on-http-response           http_response            x
+    on-server-session-end      server_session_end       -
+
+  The backend starts its trace at on-frontend-http-request where it extracts
+  the span context injected by the frontend.  Earlier request events
+  (on-client-session-start, on-frontend-tcp-request) are not needed because
+  the context is not yet available in the HTTP headers at that point.
+
+
+Context propagation
+-------------------
+
+  The frontend injects context into HTTP headers in the backend_http_request
+  scope:
+
+    span "HAProxy session"
+        inject "otel-ctx" use-headers
+
+  The backend extracts that context in its frontend_http_request scope:
+
+    extract "otel-ctx" use-headers
+    span "HAProxy session" parent "otel-ctx" root
+
+
+Span hierarchy
+--------------
+
+  Frontend (fe/):
+
+    "HAProxy session" (root)
+     +-- "Client session"
+          +-- "Frontend TCP request"
+               +-- "Frontend HTTP request"
+                    +-- "Backend TCP request"
+                         +-- "Backend HTTP request"
+
+    "HAProxy session" (root)
+     +-- "Server session"
+          +-- "TCP response"
+               +-- "HTTP response"
+
+  Backend (be/):
+
+    "HAProxy session" (root, parent: frontend's "HAProxy session")
+     +-- "Client session"
+          +-- "Frontend HTTP request"
+               +-- "Backend TCP request"
+                    +-- "Backend HTTP request"
+
+    "HAProxy session" (root)
+     +-- "Server session"
+          +-- "TCP response"
+               +-- "HTTP response"
+
+
+Running the test
+----------------
+
+From the test/ directory:
+
+  % ./run-fe-be.sh [/path/to/haproxy] [pidfile]
+
+If no arguments are given, the script looks for the haproxy binary three
+directories up from the current working directory.  The backend origin server
+must be running on 127.0.0.1:8000.
+
+The script launches both HAProxy instances in the background and waits.
+Press CTRL-C to stop both instances.
diff --git a/addons/otel/test/README-full b/addons/otel/test/README-full
new file mode 100644 (file)
index 0000000..a93d932
--- /dev/null
@@ -0,0 +1,158 @@
+Full event coverage test configuration (full/)
+==============================================
+
+The 'full' test is a standalone single-instance configuration that exercises
+every supported OTel filter event with all three signal types: traces (spans),
+metrics (instruments) and logs (log-records).
+
+It extends the 'sa' (standalone) configuration by adding the events that 'sa'
+does not cover and by attaching log-records to every scope.
+
+
+Files
+-----
+
+  full/otel.cfg    OTel filter configuration (scopes, groups, instruments)
+  full/haproxy.cfg HAProxy frontend/backend configuration
+  full/otel.yml    Exporter, processor, reader and provider definitions
+  run-full.sh      Convenience script to launch HAProxy with this config
+
+
+Events
+------
+
+The table below lists every event defined in include/event.h together with the
+scope that handles it and the signals produced by that scope.
+
+  T = Trace (span)    M = Metric (instrument)    L = Log (log-record)
+
+  Stream lifecycle events:
+
+    Event                    Scope                       T  M  L
+    ---------------------------------------------------------------
+    on-stream-start          on_stream_start             x  x  x
+    on-stream-stop           on_stream_stop              -  -  x
+    on-idle-timeout          on_idle_timeout              x  x  x
+    on-backend-set           on_backend_set               x  x  x
+
+  Request analyzer events:
+
+    Event                              Scope                          T  M  L
+    --------------------------------------------------------------------------
+    on-client-session-start            client_session_start           x  x  x
+    on-frontend-tcp-request            frontend_tcp_request           x  x  x
+    on-http-wait-request               http_wait_request              x  -  x
+    on-http-body-request               http_body_request              x  -  x
+    on-frontend-http-request           frontend_http_request          x  x  x
+    on-switching-rules-request         switching_rules_request        x  -  x
+    on-backend-tcp-request             backend_tcp_request            x  x  x
+    on-backend-http-request            backend_http_request           x  -  x
+    on-process-server-rules-request    process_server_rules_request   x  -  x
+    on-http-process-request            http_process_request           x  -  x
+    on-tcp-rdp-cookie-request          tcp_rdp_cookie_request         x  -  x
+    on-process-sticking-rules-request  process_sticking_rules_request x  -  x
+    on-http-headers-request            http_headers_request           x  x  x
+    on-http-end-request                http_end_request               x  x  x
+    on-client-session-end              client_session_end             -  x  x
+    on-server-unavailable              server_unavailable             -  -  x
+
+  Response analyzer events:
+
+    Event                              Scope                          T  M  L
+    --------------------------------------------------------------------------
+    on-server-session-start            server_session_start           x  x  x
+    on-tcp-response                    tcp_response                   x  x  x
+    on-http-wait-response              http_wait_response             x  -  x
+    on-process-store-rules-response    process_store_rules_response   x  -  x
+    on-http-response                   http_response                  x  x  x
+    on-http-headers-response           http_headers_response          x  x  x
+    on-http-end-response               http_end_response              x  x  x
+    on-http-reply                      http_reply                     x  x  x
+    on-server-session-end              server_session_end             -  x  x
+
+  Additionally, the http_response-error scope fires conditionally on the
+  on-http-response event when the response status is outside the 100-399 range,
+  setting an error status on the "HTTP response" span.
+
+  The http_response_group (http_response_1, http_response_2) and
+  http_after_response_group (http_after_response) are invoked via http-response
+  and http-after-response directives in haproxy.cfg.
+
+
+Instruments
+-----------
+
+Every instrument definition has at least one corresponding update.
+
+  Instrument name                Type       Defined in              Updated in
+  -------------------------------------------------------------------------------
+  haproxy.sessions.active        udcnt_int  on_stream_start         client_session_end
+  haproxy.fe.connections         gauge_int  on_stream_start         http_response
+  idle.count                     cnt_int    on_idle_timeout         on_idle_timeout
+  haproxy.backend.set            cnt_int    on_backend_set          on_backend_set
+  haproxy.client.session.start   cnt_int    client_session_start    client_session_end
+  haproxy.tcp.request.fe         cnt_int    frontend_tcp_request    frontend_http_request
+  haproxy.http.requests          cnt_int    frontend_http_request   http_response
+  haproxy.http.latency           hist_int   frontend_http_request   frontend_http_request,
+                                                                    http_response
+  haproxy.tcp.request.be         cnt_int    backend_tcp_request     backend_http_request
+  haproxy.http.headers.request   cnt_int    http_headers_request    http_end_request
+  haproxy.http.end.request       cnt_int    http_end_request        client_session_end
+  haproxy.server.session.start   cnt_int    server_session_start    server_session_end
+  haproxy.tcp.response           cnt_int    tcp_response            http_wait_response
+  haproxy.http.headers.response  cnt_int    http_headers_response   http_end_response
+  haproxy.http.end.response      cnt_int    http_end_response       http_reply
+  haproxy.http.reply             cnt_int    http_reply              server_session_end
+
+
+Span hierarchy
+--------------
+
+  Request path:
+
+    "HAProxy session" (root)
+     +-- "Client session"
+          +-- "Frontend TCP request"
+               +-- "HTTP wait request"
+                    +-- "HTTP body request"
+                         +-- "Frontend HTTP request"  [link: "HAProxy session"]
+                              +-- "Switching rules request"
+                                   +-- "Backend TCP request"
+                                        +-- "Backend HTTP request"
+                                             +-- "Process server rules request"
+                                                  +-- "HTTP process request"
+                                                       +-- "TCP RDP cookie request"
+                                                            +-- "Process sticking rules request"
+                                                                 +-- "HTTP headers request"
+                                                                      +-- "HTTP end request"
+
+  Response path:
+
+    "HAProxy session" (root)
+     +-- "Server session"  [link: "HAProxy session", "Client session"]
+          +-- "TCP response"
+               +-- "HTTP wait response"
+                    +-- "Process store rules response"
+                         +-- "HTTP response"
+                              +-- "HTTP headers response"
+                                   +-- "HTTP end response"
+                                        +-- "HTTP reply"
+
+  Auxiliary spans:
+
+    "HAProxy session"
+     +-- "Backend set"
+     +-- "heartbeat"              (on-idle-timeout, periodic)
+     +-- "HAProxy response"       (http_after_response_group, on error)
+
+
+Running the test
+----------------
+
+From the test/ directory:
+
+  % ./run-full.sh [/path/to/haproxy] [pidfile]
+
+If no arguments are given, the script looks for the haproxy binary three
+directories up from the current working directory.  The backend origin server
+must be running on 127.0.0.1:8000.
diff --git a/addons/otel/test/README-sa b/addons/otel/test/README-sa
new file mode 100644 (file)
index 0000000..f5a1c12
--- /dev/null
@@ -0,0 +1,134 @@
+Standalone test configuration (sa/)
+=====================================
+
+The 'sa' test is a standalone single-instance configuration that
+exercises most HAProxy filter events with spans, attributes, events,
+links, baggage, status, metrics, logs and groups.  It represents the
+most comprehensive single-instance configuration and is used as the
+worst-case scenario in speed tests.
+
+Six events are not covered by this configuration: on-backend-set,
+on-http-headers-request, on-http-end-request, on-http-headers-response,
+on-http-end-response and on-http-reply.  The 'full' configuration
+extends 'sa' with those events.
+
+
+Files
+------
+
+  sa/otel.cfg    OTel filter configuration (scopes, groups, instruments)
+  sa/haproxy.cfg HAProxy frontend/backend configuration
+  sa/otel.yml    Exporter, processor, reader and provider definitions
+  run-sa.sh      Convenience script to launch HAProxy with this config
+
+
+Events
+-------
+
+  T = Trace (span)    M = Metric (instrument)    L = Log (log-record)
+
+  Stream lifecycle events:
+
+    Event                    Scope                       T  M  L
+    ---------------------------------------------------------------
+    on-stream-start          on_stream_start             x  x  x
+    on-stream-stop           on_stream_stop              -  -  -
+    on-idle-timeout          on_idle_timeout              x  x  x
+
+  Request analyzer events:
+
+    Event                              Scope                          T  M  L
+    --------------------------------------------------------------------------
+    on-client-session-start            client_session_start           x  -  -
+    on-frontend-tcp-request            frontend_tcp_request           x  -  -
+    on-http-wait-request               http_wait_request              x  -  -
+    on-http-body-request               http_body_request              x  -  -
+    on-frontend-http-request           frontend_http_request          x  x  x
+    on-switching-rules-request         switching_rules_request        x  -  -
+    on-backend-tcp-request             backend_tcp_request            x  -  -
+    on-backend-http-request            backend_http_request           x  -  -
+    on-process-server-rules-request    process_server_rules_request   x  -  -
+    on-http-process-request            http_process_request           x  -  -
+    on-tcp-rdp-cookie-request          tcp_rdp_cookie_request         x  -  -
+    on-process-sticking-rules-request  process_sticking_rules_request x  -  -
+    on-client-session-end              client_session_end             -  x  -
+    on-server-unavailable              server_unavailable             -  -  -
+
+  Response analyzer events:
+
+    Event                              Scope                          T  M  L
+    --------------------------------------------------------------------------
+    on-server-session-start            server_session_start           x  -  -
+    on-tcp-response                    tcp_response                   x  -  -
+    on-http-wait-response              http_wait_response             x  -  -
+    on-process-store-rules-response    process_store_rules_response   x  -  -
+    on-http-response                   http_response                  x  x  -
+    on-server-session-end              server_session_end             -  -  -
+
+  Additionally, the http_response-error scope fires conditionally on the
+  on-http-response event when the response status is outside the 100-399
+  range, setting an error status on the "HTTP response" span.
+
+  The http_response_group (http_response_1, http_response_2) and
+  http_after_response_group (http_after_response) are invoked via
+  http-response and http-after-response directives in haproxy.cfg.
+
+
+Instruments
+------------
+
+  Instrument name            Type       Defined in              Updated in
+  --------------------------------------------------------------------------
+  haproxy.sessions.active    udcnt_int  on_stream_start         client_session_end
+  haproxy.fe.connections     gauge_int  on_stream_start         http_response
+  idle.count                 cnt_int    on_idle_timeout         on_idle_timeout
+  haproxy.http.requests      cnt_int    frontend_http_request   http_response
+  haproxy.http.latency       hist_int   frontend_http_request   frontend_http_request,
+                                                                http_response
+
+
+Span hierarchy
+---------------
+
+  Request path:
+
+    "HAProxy session" (root)
+     +-- "Client session"
+          +-- "Frontend TCP request"
+               +-- "HTTP wait request"
+                    +-- "HTTP body request"
+                         +-- "Frontend HTTP request"  [link: "HAProxy session"]
+                              +-- "Switching rules request"
+                                   +-- "Backend TCP request"
+                                        +-- "Backend HTTP request"
+                                             +-- "Process server rules request"
+                                                  +-- "HTTP process request"
+                                                       +-- "TCP RDP cookie request"
+                                                            +-- "Process sticking rules request"
+
+  Response path:
+
+    "HAProxy session" (root)
+     +-- "Server session"  [link: "HAProxy session", "Client session"]
+          +-- "TCP response"
+               +-- "HTTP wait response"
+                    +-- "Process store rules response"
+                         +-- "HTTP response"
+
+  Auxiliary spans:
+
+    "HAProxy session"
+     +-- "heartbeat"              (on-idle-timeout, periodic)
+     +-- "HAProxy response"       (http_after_response_group, on error)
+
+
+Running the test
+-----------------
+
+From the test/ directory:
+
+  % ./run-sa.sh [/path/to/haproxy] [pidfile]
+
+If no arguments are given, the script looks for the haproxy binary three
+directories up from the current working directory.  The backend origin
+server must be running on 127.0.0.1:8000.