Compare commits

...

149 commits

Author SHA1 Message Date
William Lallemand
8d2381c5ee MEDIUM: ssl: add FIPS signature algorithm check for AWS-LC
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Add ssl_fips_check_sigalgs() which validates the configured signature
algorithm list against the FIPS-approved set: ECDSA on NIST P-curves
with SHA-256/384/512, RSA-PSS (rsae and pss variants) with SHA-256/
384/512, and RSA-PKCS1 with SHA-256/384/512.  SHA-1 based algorithms
and non-FIPS primitives (ed25519, ed448) are rejected.

The check uses the same strchr-based string parsing as
ssl_fips_check_ciphersuites().  A NULL list is silently accepted since
the global defaults were already overwritten with FIPS values at init
time.

The check is called right after SSL_CTX_set1_sigalgs_list() and
SSL_CTX_set1_client_sigalgs_list() in both the bind
(ssl_sock_prepare_ctx) and server (ssl_sock_prepare_srv_ssl_ctx)
configuration paths.
2026-07-10 19:42:03 +02:00
William Lallemand
46c3edcb16 MEDIUM: ssl: set FIPS-approved sigalgs defaults for AWS-LC FIPS builds
When AWS-LC is built in FIPS mode, unconditionally override the
compile-time signature algorithm defaults with the FIPS-approved set
before config parsing. Explicit ssl-default-{bind,server}-sigalgs
keywords in the global section still take precedence over these
defaults.

The approved set is defined as macros in include/haproxy/defaults.h
alongside the existing CONNECT/LISTEN_DEFAULT_FIPS_CIPHERS family:
  CONNECT/LISTEN_DEFAULT_FIPS_SIGALGS        - ECDSA (P-256/384/521),
                                               RSA-PSS and RSA-PKCS1
                                               with SHA-256/384/512
  CONNECT/LISTEN_DEFAULT_FIPS_CLIENT_SIGALGS - same set for client
                                               certificate sigalgs

SHA-1 based algorithms and non-FIPS primitives (ed25519, ed448) are
excluded from the defaults.
2026-07-10 19:42:03 +02:00
William Lallemand
96c7759f3b MEDIUM: ssl: add FIPS elliptic curve check for AWS-LC
Add ssl_fips_check_curves() which validates the configured curve list
against the FIPS-approved NIST P-curves (P-256, P-384, P-521).  Each
colon-separated name is resolved to a NID via OBJ_txt2nid() so all
standard aliases (P-256, prime256v1, secp256r1) are handled uniformly.
A NULL list is silently accepted since the global defaults were already
overwritten with FIPS values at init time.

The check is called right after SSL_CTX_set1_curves_list() in both the
bind (ssl_sock_prepare_ctx) and server (ssl_sock_prepare_srv_ssl_ctx)
configuration paths.
2026-07-10 19:42:03 +02:00
William Lallemand
3b2cd5f9fb MEDIUM: ssl: set FIPS-approved curve defaults for AWS-LC FIPS builds
When AWS-LC is built in FIPS mode, unconditionally override the
compile-time curve defaults with the FIPS-approved NIST P-curves
before config parsing. Explicit ssl-default-{bind,server}-curves
keywords in the global section still take precedence over these
defaults.

The approved set is defined as macros in include/haproxy/defaults.h
alongside the existing CONNECT/LISTEN_DEFAULT_FIPS_CIPHERS family:
  CONNECT/LISTEN_DEFAULT_FIPS_CURVES - P-256, P-384, P-521

This ensures that internal servers (httpclient, Lua SSL sockets) that
inherit global defaults also operate with FIPS-compliant curve lists
without requiring explicit configuration.
2026-07-10 19:42:03 +02:00
William Lallemand
29f5126317 MEDIUM: ssl: add FIPS TLS 1.3 ciphersuite check for AWS-LC
AWS-LC does not expose TLS 1.3 ciphersuites set via SSL_CTX_set_ciphersuites()
through SSL_CTX_get_ciphers(), so the existing NID-based cipher check in
ssl_fips_check_ciphers() cannot catch non-FIPS TLS 1.3 suites.  This is
further compounded by a defect in the AWS-LC-FIPS 3.x branch where TLS 1.3
ciphers are missing from SSL_get_ciphers() entirely (fixed in
https://github.com/aws/aws-lc/pull/2092), making any SSL_CTX-based
inspection unreliable across versions.

Add ssl_fips_check_ciphersuites() which validates the ciphersuite string
directly against a FIPS-approved allowlist (TLS_AES_128_GCM_SHA256 and
TLS_AES_256_GCM_SHA384).  A NULL string is silently accepted since the
global defaults were already overwritten with FIPS values at init time.

The new check is called right after SSL_CTX_set_ciphersuites() in both
the bind (ssl_sock_prepare_ctx) and server (ssl_sock_prepare_srv_ssl_ctx)
configuration paths.
2026-07-10 19:42:03 +02:00
William Lallemand
4fbc3e36c1 MEDIUM: ssl: set FIPS-approved cipher defaults for AWS-LC FIPS builds
When AWS-LC is built in FIPS mode, unconditionally override the
compile-time cipher defaults with FIPS-approved sets before config
parsing. Explicit ssl-default-{bind,server}-ciphers{suites} keywords
in the global section still take precedence over these defaults.

The approved sets are defined as macros in include/haproxy/defaults.h
alongside the existing CONNECT/LISTEN_DEFAULT_CIPHERS family:
  CONNECT/LISTEN_DEFAULT_FIPS_CIPHERS     - AES-128-GCM-SHA256 and
                                            AES-256-GCM-SHA384 (TLS 1.2)
  CONNECT/LISTEN_DEFAULT_FIPS_CIPHERSUITES - TLS_AES_128_GCM_SHA256 and
                                             TLS_AES_256_GCM_SHA384 (TLS 1.3)

This ensures internal servers (httpclient, Lua SSL sockets) that
inherit global defaults also operate with FIPS-compliant cipher lists
without requiring explicit configuration.
2026-07-10 19:42:03 +02:00
William Lallemand
9e2809508b MEDIUM: ssl: add FIPS TLS 1.2 cipher check for AWS-LC
Add ssl_fips_check_ciphers() to check the cipher list already loaded
into an SSL_CTX against the FIPS-approved bulk cipher NID allowlist
(AES-128-GCM, AES-256-GCM).  The check is run unconditionally after
SSL_CTX_set_cipher_list() for both bind and server contexts.

The function reuses the fips_obj_info() helper introduced with the
version check to emit precise 'type proxy/name [file:line]' error
messages and returns ERR_ALERT|ERR_ABORT|ERR_FATAL on violation.
2026-07-10 19:42:03 +02:00
William Lallemand
9ac590b55e MEDIUM: ssl: introduce src/fips.c with TLS version check
Add src/fips.c and include/haproxy/fips.h to centralise FIPS compliance
checks for AWS-LC builds.

ssl_fips_check_version() verifies that ssl-min-ver is not set below
TLS 1.2, which AWS-LC in FIPS mode would refuse to negotiate.

The check accepts an enum obj_type * so it can resolve the owning
bind/server and produce a precise error message including the proxy
and object name in the standard HAProxy 'type proxy/name' form.
2026-07-10 19:42:03 +02:00
Maxime Henrion
475a69d224 MEDIUM: cache: add support for hints-only HTTP caches
The "early-hints" option can now be set to "only", in which case the
cache will only store data relevant to the emission of 103 Early Hints
responses.

Hint records are extracted on the fly from the live response HTX
rather than going through a full-storage-then-strip cycle: the entry
is created as CACHE_EF_STRIPPED directly, with hint records written
past the cache_entry header at storage time. Response bodies are
never stored.

Because entries are stripped from the start, eviction in this mode
never has to strip a full entry; the full LRU stays empty and
cache_make_room() falls through to evicting the oldest stripped entry
when the cache fills up.

The max-object-size check (which gates caching based on the upstream-
announced Content-Length) is bypassed in this mode: the actual stored
entry is just the hint records (typically under 1 KB), so applying it
to the body size would needlessly discard hints for arbitrarily large
responses.

As a hints-only entry is never served as a response, the backend
response is forwarded unchanged in this mode: in particular its Age
header is kept rather than stripped as it is for a stored response.
2026-07-10 16:42:35 +02:00
Maxime Henrion
39233e0a67 MINOR: cache: factor cache_extract_link_hints out of cache_extract_hints
Move the per-Link-value processing into a new cache_extract_link_hints()
helper. cache_extract_hints() continues to walk the cached row's HDR
blocks and now invokes the helper once per Link header it finds.

No behaviour change. This prepares the helper for reuse by an upcoming
hints-only storage path that extracts Link headers directly from the
live response HTX rather than from a cached row.
2026-07-10 16:42:35 +02:00
Maxime Henrion
66d5d6a54b REGTESTS: cache: validate the emission of 103s
Add four regtests to validate the behavior of the HTTP cache when early
hints are enabled.

- early_hints.vtc: covers 103 emission on stripped entries and that the
  "no-early-hints" rule keyword suppresses it.
- early_hints_ratio.vtc: covers cap-driven eviction of hints entries when
  the ratio is reached.
- early_hints_parsing.vtc: pins down the (deliberately strict) Link
  header parsing behaviour of link_is_hint() so future relaxations
  cannot silently change hint detection.
- early_hints_multiblock.vtc: checks that hints are still extracted when
  the relevant Link headers span more than one cache block.
2026-07-10 16:42:35 +02:00
Maxime Henrion
51377f9dfb MINOR: cache: allow customizing ratio for early hints
It is now possible to give a "ratio" parameter to the "early-hints"
option to indicate what percentage of blocks to reserve for hints-only
entries. Defaults to 25%.
2026-07-10 16:42:35 +02:00
Maxime Henrion
954614c03a MINOR: cache: allow opting out of early hints at the rule level
A "cache-use" directive can now specify the "no-early-hints" keyword
to suppress 103 Early Hints emission for matching requests, when used
with a cache that has the "early-hints" setting enabled.
2026-07-10 16:42:35 +02:00
Maxime Henrion
6f8c422252 MINOR: cache: add a counter for cache hits serving early hints
Add a cache_hint_hits counter to the per-proxy HTTP stats, incremented
each time the cache emits a 103 Early Hints response. It is reported
in the CSV/typed outputs, on the stats page next to the cache hits,
and exported to prometheus as http_cache_hint_hits_total. Since the
new field changes the shm stats file mapping, its minor version is
bumped along with the object size assertion.
2026-07-10 16:42:35 +02:00
Maxime Henrion
25fcee5b0c MEDIUM: cache: emit early hints if configured to do so
The HTTP cache now supports sending 103 Early Hints responses if
enabled, without the need for explicit per-rule configuration.

Two paths feed the 103 response depending on the matched entry. A
stripped entry (CACHE_EF_STRIPPED) carries its Link records
pre-extracted alongside the cache entry header and is copied out of
the row as-is. A complete entry only carries the cached HTX; the
Link headers are walked on the fly at request time, deliberately
avoiding the extra storage cost of duplicating them alongside the
body.

The on-the-fly path has a cost: a workload where many hits land on
expired complete entries re-parses the cached response on each hit.
To bound this, the first such hit also strips the entry in place
when conditions allow (the hints pool is below its cap, and the row
is not currently being read by another request); subsequent hits for
the same URL then take the cheap pre-extracted path.

To let the cache-use lookup reach that logic, get_entry() now returns
expired entries to callers passing delete_expired=0; such callers are
responsible for checking entry->expire themselves.
2026-07-10 16:42:35 +02:00
Maxime Henrion
7f3c30d96c MINOR: http: factor 103 emission into start/end helpers
Pull the 103 emission code out of http_action_early_hint() and split
it into http_early_hint_start() (open the response and return the htx)
and http_early_hint_end() (close with EOH and forward). The split
shape lets callers add their own Link headers in between, which is
what the HTTP cache will need when it gets support for sending 103
Early Hints from cached responses.
2026-07-10 16:42:35 +02:00
Maxime Henrion
e1b02eba9b MINOR: cache: indicate whether entries are stripped or not
A full cache entry now shows "type:full", whereas stripped entries used
to emit 103 responses show "type:hints".
2026-07-10 16:42:35 +02:00
Maxime Henrion
77c4de1ec2 MEDIUM: cache: early hints-aware eviction code
We now use the make_room callback from shctx to customize the eviction
procedure. We attempt to balance eviction between hints and full-blown
entries by capping the number of blocks used by hints entries to 25% of
the size of the cache. Upon eviction of a full entry, we first try to
strip it to a hints-only entry if possible.

A Link header's value (which may carry multiple comma-separated links)
is limited to 1024 bytes for hint extraction. Longer values are skipped
entirely and contribute no hint records.
2026-07-10 16:42:35 +02:00
Maxime Henrion
b089325f0e MINOR: cache: track full and hints entries in per-pool LRU lists
Introduce two LRU lists in struct cache (full_lru and hints_lru) and an
"lru" link in struct cache_entry, plus a CACHE_EF_STRIPPED flag to
mark hints-only entries. A new cache_row_reattach() helper places each
entry in the appropriate LRU when a row is returned to the cold pool.

No behaviour change yet: the lists are populated but no code consumes
them. The next commit uses them to drive early hints-aware eviction.
2026-07-10 16:42:35 +02:00
Maxime Henrion
731b71363f MINOR: shctx: allow consumers to customize eviction strategy
This introduces a make_room() callback that consumers can provide in
order to implement more specific eviction strategies. To be used in the
HTTP cache code. This goes in hand with a new shctx_row_truncate()
function that allows consumers to truncate a row, which can be used in
the make_room() callback.
2026-07-10 16:42:35 +02:00
Maxime Henrion
2a27062f9a MINOR: cache: add helper functions for early hints support
Add the hint_rels array, rel_is_hint(), and link_is_hint() functions
for identifying Link header values eligible for early hints emission.
2026-07-10 16:42:35 +02:00
Maxime Henrion
f4da8e0299 MINOR: cache: add config options for early hints support
Support for 103 early hints responses can now be enabled at the HTTP
cache level with "early-hints on" - defaults to off.
2026-07-10 16:42:35 +02:00
Maxime Henrion
bbbd7909bc MINOR: cache: minor changes ahead of support for sending early hints
Add a flags field to struct cache_entry and replace the complete
field with a CACHE_EF_COMPLETE flag.

Likewise, add a flags field to struct cache and replace the
vary_processing_enabled configuration boolean with a
CACHE_CF_VARY_PROCESSING flag.
2026-07-10 16:42:35 +02:00
Maxime Henrion
ef73a1f64a MINOR: http: add two header parsing functions
Add http_next_hdr_value() to iterate over the comma-separated values
that some headers contain. Add http_get_hdr_param() to iterate over the
';'-separated parameters that may follow a value.

Currently unused but will be used for parsing Link headers in the cache
to support 103 Early Hints, and could also clean up Cache-Control
parsing among others.
2026-07-10 16:42:35 +02:00
Maxime Henrion
dc50a814c7 BUG/MEDIUM: cache: reattach the row when a secondary entry is incomplete
In http_action_req_cache_use() with Vary, the row of the secondary entry
matching the request is detached from the avail list for reading. Since
get_secondary_entry() only rejects expired entries, that secondary may
be incomplete (still being written by another stream), and in that case
the request was forwarded without reattaching the row, leaking its
blocks out of the avail list (refcount and nbav never restored). Over a
Vary workload this eventually prevents the cache from reserving any row.

Reattach the row before returning, as the other paths that give up on
the cache entry do.

Present since the Vary support was added in 2.4 (1785f3dd9); to be
backported as far as 2.6.
2026-07-10 16:42:35 +02:00
Maxime Henrion
912b82d078 DOC: stats: document the per-proxy byte count fields in the CSV list
Fields 114 to 117 (reqbin, reqbout, resbin, resbout) were added to the
CSV output but never listed in the stats description of management.txt.

This should be backported up to 3.3.
2026-07-10 16:42:35 +02:00
Maxime Henrion
0386ec2544 MINOR: shctx: clamp shctx_row_data_get() reads against the offset
The length clamp capped the request to the whole row length but ignored
<offset>, so a read starting past the beginning of the row could run
beyond the stored data and copy stale bytes from the reserved tail of
its blocks. No current caller reads past first->len so this was never
triggered, but the helper is public and its contract does not forbid it.

Clamp against (first->len - offset) instead, returning early when the
offset is already at or past the stored data.

This may be backported along with the shctx_row_data_get() offset fix.
2026-07-10 16:42:35 +02:00
Maxime Henrion
6750c36adf BUG/MINOR: shctx: fix shctx_row_data_get() when offset exceeds a block
The loop advanced the block pointer only after the copy, but skipped
blocks preceding <offset> with a continue, which bypassed that advance.
As a result any read starting past the first block copied nothing.

This notably affects conditional requests: should_send_notmodified_response()
reads the stored ETag at entry->etag_offset, so a cached response whose
ETag header sits beyond the first block fails revalidation and is served
in full (200) instead of 304.

Rewrite the loop as a for() so the block pointer always advances,
regardless of whether the current block is copied or skipped.

This should be backported as far as 2.6.
2026-07-10 16:42:35 +02:00
Willy Tarreau
f34517a1aa BUG/MEDIUM: stats: subject "stats admin" accesses to "stats scope" filtering
It was reported by Red Hat in partnership with AISLE Research that
proxies updated via a stats page in "stats admin" mode were not subject
to the filtering of "stats scope".

It is totally true and was never meant to be when "stats scope" was
added in 1.2 in 2006 while "stats admin" arrived 5 years later in 1.4,
nor was it even implied by the doc, but it is easy to see how someone
who would combine the two could expect them to work together:

- "stats scope" only limits which proxies are shown, the purpose being
  to avoid polluting the page with a long list of auxiliary proxies that
  take ages to render (e.g. those dedictated to stick-tables etc). A
  common use case is to only show known frontends so that numerous
  backends are not listed, it's most often used on public access.

- "stats admin" is used to permit to change a proxy or a server's
  state and is protected using HTTP Basic auth, cleartext passwords
  by default, and is mostly meant to be used from trusted sources,
  hence private access.

The two are almost never seen together (no single occurrence found
in github issues for 92 occurrences of "stats admin" and 5 of
"stats scope"), but admittedly, if someone were to combine the two,
seeing buttons on the listed proxies only, they would surely expect
to only be able to act on those.

So in order to clear this void, this commit does the following upon
a POST:
  - when a scope is defined, the requested proxy must exist and match
    the list of permitted ones, otherwise a DENY is returned. This
    ensures that the POST cannot be abused either to infer the existence
    of a given proxy name.
  - when no scope is defined, a non-existing proxy continues to return
    an "unknown proxy" error.

The doc was updated to explain this new behavior. This commit should
be backported to stable versions, along with the previous patch it
depends on:

   MINOR: stats: factor the proxy vs scope check into its own function

The stats code was split between 2.9 and 3.0, so the changes must all
be performed in stats.c before 3.0. Also, ctx->http_px does not exist
earlier, but http_px is simply the calling stream's backend (s->be at
some places).

Thanks to Red Hat and AISLE Research for reporting this.
2026-07-10 16:27:23 +02:00
Willy Tarreau
9a8f923b02 MINOR: stats: factor the proxy vs scope check into its own function
We'll need to check for matching between a proxy and a stats scope at
a few more places, so let's first move the code out of its current
function (stats_dump_proxy_to_buffer) into its own function
(stats_proxy_in_scope). At this point it doesn't change anything.
Note that this will have to be backported as this will be used with
a subsequent fix.
2026-07-10 16:27:23 +02:00
Christopher Faulet
16d259c5db BUG/MEDIUM: applet: Reenable reads in applet context if requesting a connection
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
When a applet is waiting for a connection, like Socket applet in lua, reads
are blocked. When the connection is finally available , a read activity is
reported and reads are reenabled (flag SE_FL_APPLET_NEED_CONN is removed).

However, most of time (always ?), this operation is performed in the context
of the connection and the applet is woken up. It means the task expiration
date is not updated where the read activity is reported. It is an issue for
small timeouts because the task may be queued in past, triggering a BUG_ON
in sc_notify(). In fact, a read activity must never be reported in another
context of the endpoint itself or the upper stream, specifically to properly
set the task expiration date.

To fix the issue, in sc_chk_rcv(), the applet is only woken up in that case
and the reads are re-enabled when the applet is executed. For applets using
the new API, it is performed in sc_appet_sync_recv(). For legacy applets, it
is directly performed in task_run_applet().

This patch is related to #3442. It must be backported as far as 2.8. On the
2.8, only task_run_applet must be fixed because sc_applet_sync_recv() does
not exist.
2026-07-10 12:03:18 +02:00
Christopher Faulet
55a1bb9e09 BUG/MINOR: hlua: Apply socket timeout on server side only
In lua, the timeout that can be set on an Socket applet was applied on
client side and server side. In fact, it must only be applied on server
side, to apply a timeout on the connection. The client side is the applet.

This patch should fix the issue #3442. It must be backported to all
supported versions.
2026-07-10 11:49:36 +02:00
Olivier Houchard
0a11d75c1b BUILD: task: Fix build when no 8B CAS is available at all
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
clang (rightfully) complains that when neither HA_CAS_IS_8B nor
HA_HAVE_CAS_DW is defined, there is a mismatch when we're doing a CAS,
as we're mixing unsigned int and int.
To fix that, just turn old_state to an unsigned int, as it should be.

This should fix github issue #3441
2026-07-09 16:05:54 +02:00
Olivier Houchard
cfe708d049 BUILD: haload: Increase a buffer size so that gcc will stop complaining
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
gcc 16 whines that in human_number(), the calls to snprintf() could lead
to a truncated output because the buffer is not big enough. This is not
really true, because we always have a limited number of digits, but gcc
can't figure that out, so just bump the buffer size from 5 bytes to 8
bytes to make gcc happy.
2026-07-08 19:11:17 +02:00
Willy Tarreau
caaacb4fc1 [RELEASE] Released version 3.5-dev2
Released version 3.5-dev2 with the following main changes :
    - MINOR: proxy: permit to report version info for option deprecation
    - MAJOR: proxy: remove support for "dispatch" and "transparent" proxy keywords
    - MEDIUM: cli/show-fd: no longer accept filtering for dispatch mode
    - CLEANUP: connection: remove some checks for objt_proxy(conn->target)
    - CLEANUP: backend: drop checks for OBJ_TYPE_PROXY in connect() code
    - CLEANUP: trace: remove backend retrieval attempt from conn->target
    - MAJOR: ot: remove deprecated OpenTracing support
    - BUG/MEDIUM: h3: fix trace crash on frontend response headers
    - CI: github: remove OpenTracing leftovers
    - BUG/MEDIUM: mux_quic: fix memory leak of rx app_buf on stream free
    - MINOR: ssl: export ssl_sock_init_srv()
    - MEDIUM: httpclient: initialize the httpclient with default SSL values
    - BUG/MINOR: hq-interop: fix transcoding of wrapping response buffer
    - BUG/MINOR: hq-interop: support transcoding of absolute URI
    - BUG/MINOR: h3: adjust HTTP headers traces
    - MINOR: mux_quic: add minimal traces for QUIC MUX init/release
    - MINOR: hq-interop: add request start-line traces
    - MINOR: hq-interop: trace transcoding of response status line
    - MINOR: hq-interop: trace HTX headers
    - BUG/MEDIUM: server: initialise agent.health in srv_settings_init()
    - BUG/MINOR: sample: set SMP_F_CONST on srv_name fetch
    - MINOR: server: distinguish name references with new SRV_F_NAME_REFD flag
    - MEDIUM: server: add 'set server name' CLI command for runtime server renaming
    - REGTESTS: server: add test for 'set server name' CLI command
    - DOC: server: document 'set server name' CLI command
    - BUG/MEDIUM: servers: Use a refcount for port_range and free it properly
    - MINOR: hbuf: new lightweight hbuf API
    - MINOR: init: add no listener mode
    - MINOR: trace: add definitions for haload streams
    - MINOR: hldstream: add definition of hldstream struct objects
    - MINOR: obj_type: add OBJ_TYPE_HALOAD for haload stream objects
    - MINOR: stconn: add sc_hastream() and __sc_hastream() helpers
    - MINOR: stconn: export sc_new()
    - MINOR: server: export functions used during server initialization
    - MINOR: haload: import source code and documentation
    - MINOR: log: add app_log_raw() and send_log_raw() for binary-safe logging
    - BUG/MINOR: init: fix default global settings being overwritten by -G
    - BUG/MINOR: tools: fix invalid character detection in strl2ic()
    - BUG/MAJOR: htx: Don't swap buffers for empty HTX message with an error
    - CLEANUP: applet/http-client: Don't needlessly copy HTX flags after htx_xfer()
    - BUG/MINOR: mux-quic: Fix handling EOM after in qcs_http_rcv_buf()
    - BUG/MINOR: http-htx: Don't by-pass HTX API when merging cookie values
    - BUG/MEDIUM: h3: fix parser desync on error with multiple frames
    - BUG/MINOR: mux_quic: prevent multiple STOP_SENDING emission per stream
    - BUILD: quic: workaround a gcc bug saying "maybe used uninitialized" when USE_TRACE=0
    - CLEANUP: traces: get rid of a few rare empty args in TRACE calls
    - MINOR: compiler: add a macro to ignore all arguments
    - MINOR: trace: always pretend to use args when disabled
    - BUILD: ssl: avoid a wrong null deref warning in ssl_sock_handshake
    - CLEANUP: haproxy: remove -dt parsing and help when !USE_TRACE
    - CLEANUP: mux-h2/traces: remove unused trace code when building without USE_TRACE
    - CLEANUP: debug/trace: remote "debug dev trace" when USE_TRACE is not set
    - BUG/MINOR: trace/quic_frame: use buf, not trace_buf in chunk_frm_appendf()
    - CLEANUP: trace/h3: allow to disable traces in H3
    - CLEANUP: trace/config: do not register section "traces" with USE_TRACE=0
    - CLEANUP: trace/tree-wide: drop trace decoding/definition when USE_TRACE=0
    - BUILD: makefile: only build trace.c and ssl_trace.c when USE_TRACE is set
    - BUILD: makefile: add macros enable_opts and disable_opts
    - BUILD: makefile: add an option to enable or disable HTTP/2 (USE_H2)
    - BUILD: makefile: add an option to enable or disable FCGI (USE_FCGI)
    - BUILD: makefile: add an option to enable or disable SPOE (USE_SPOE)
    - BUILD: makefile: add a new generic target "tiny"
    - BUG/MEDIUM: mux_quic: do not free QCS if STOP_SENDING to sent
    - MINOR: mux_quic: use separate error code for STOP_SENDING
    - MINOR: mux_quic: adjust shut stream callback
    - BUG/MEDIUM: mux_quic: complete stream shutdown for read channel
    - BUG/MINOR: quic: ignore STREAM after MUX closure on BE side
    - BUG/MEDIUM: fd: Fix a deadlock when closing other tgroups fds
    - MEDIUM: quic: remove deprecated keywords
    - DEV: patchbot: keep the review start in sync with the radios on reload
    - DEV: patchbot: only display the first 8 chars of the commit id
    - DEV: patchbot: pass the branch version to the generated page
    - DEV: patchbot: let the page fetch the shared review state
    - DEV: patchbot: let the page save the review edits to the server
    - DEV: patchbot: let the page edit and delete whole notes
    - DEV: patchbot: gray the save button when there is nothing to save
    - DEV: patchbot: don't pretend a save succeeded when the server ignored it
    - DEV: patchbot: tolerate polluted save responses and show server warnings
    - DEV: patchbot: repeat the syncing buttons at the bottom of the page
    - DEV: patchbot: update: add an awk backend to persist review edits
    - DEV: patchbot: update: return the stored overlay as JSON on GET
    - DEV: patchbot: update: support replacing a whole note blob (setnotes)
    - DEV: patchbot: update: report git commit failures in the save response
    - DEV: patchbot: update: report the exact git error to the user
    - DEV: patchbot: update: never write to stderr, thttpd sends it first
    - DEV: patchbot: update: name the touched commits in the storage messages
    - DEV: patchbot: document the shared review persistence
    - BUG/MINOR: haload: fix spurious task wakeup in hld_strm_task()
    - BUG/MINOR: hbuf: treat unexpected escape sequences as literals
    - BUG/MEDIUM: tcpcheck: Add proxy used for healthcheck sections in proxies list
    - BUG/MINOR: sample: Fix a possible underflow on be2hex for large chunk size
    - MINOR: chunks: Add function to get a large/regular chunk depending on a buffer
    - BUG/MEDIUM: chunk: Review chunks usage to not retrieve a large buffer by error
    - MINOR: htx: Add a field to save the headers data size
    - MEDIUM: htx: Be sure size of headers never exceed regular buffer on update
    - BUG/MINOR: stream: Fix custom timeouts initialization when setting backend
    - REGTESTS: Improve script testing the set-timeout action
    - BUG/MINOR: stream: Fix custom max-retries initialization when setting backend
    - MINOR: stream: Add be_max_retries/cur_max_retries sample fetch functions
    - BUG/MINOR: http-conv: Make url-dec failed if no space for trailing null byte
    - MAJOR: mworker: remove deprecated "master-worker" global keyword
    - DOC: haterm: add a missing 'haterm' build target on an example
    - DOC: readme: add a pointer to haterm/haload docs
    - MINOR: ocsp: Do not see ocsp loading failures as fatal anymore
    - REGTESTS: Remove unused `add_range_to_test_list` function from `scripts/run-regtests.sh`
    - REGTESTS: Remove unused `_version` function from `scripts/run-regtests.sh`
    - REGTESTS: Migrate REQUIRE_OPTION to `haproxy -cc`
    - REGTESTS: Remove support for `REQUIRE_OPTION` from scripts/run-regtests.sh
    - REGTESTS: Migrate `REQUIRE_SERVICE=prometheus-exporter` to a `feature(PROMEX)` check
    - REGTESTS: Remove support for `REQUIRE_SERVICE` from scripts/run-regtests.sh
    - CI: github: update vmactions/freebsd-vm to 14.4
    - MINOR: haload: move statistics header printing to mtask_cb
    - MINOR: haterm: add note about QUIC usage on SSL port
    - MINOR: haload: allow "0" shortcut for IPv4 bind address
2026-07-08 18:26:54 +02:00
Frederic Lecaille
3248ba100d MINOR: haload: allow "0" shortcut for IPv4 bind address
The address parser was failing when using "0" as a shortcut for INADDR_ANY.
This patch intercepts the "0" prefix and normalizes it to "0.0.0.0"
before the address is parsed.
2026-07-08 17:35:34 +02:00
Frederic Lecaille
7f37cf8cc0 MINOR: haterm: add note about QUIC usage on SSL port
Update the usage message to clarify that QUIC is automatically enabled
on the SSL port when supported by the build.

Should be backported to 3.4.
2026-07-08 17:35:34 +02:00
Frederic Lecaille
0792e211e3 MINOR: haload: move statistics header printing to mtask_cb
Move the header printing logic from hld_init to mtask_cb using a static flag.
This ensures the header is displayed only when the main task starts
processing, avoiding issues where the header might be printed before other
haproxy initialization messages or unexpected early exits.
2026-07-08 17:35:34 +02:00
William Lallemand
1d6138909e CI: github: update vmactions/freebsd-vm to 14.4
Update the freebsd image to 14.4
2026-07-08 17:06:00 +02:00
Tim Duesterhus
3a3a347c72 REGTESTS: Remove support for REQUIRE_SERVICE from scripts/run-regtests.sh
This is no longer used in tests and thus can be removed from the test runner.
2026-07-08 14:50:25 +02:00
Tim Duesterhus
e4dceb44a3 REGTESTS: Migrate REQUIRE_SERVICE=prometheus-exporter to a feature(PROMEX) check
The prometheus exporter is a first class citizen now. This keeps consistency
with the other tests.
2026-07-08 14:50:25 +02:00
Tim Duesterhus
1e36317346 REGTESTS: Remove support for REQUIRE_OPTION from scripts/run-regtests.sh
This is no longer used in tests and thus can be removed from the test runner.
2026-07-08 14:50:25 +02:00
Tim Duesterhus
aebe561d4d REGTESTS: Migrate REQUIRE_OPTION to haproxy -cc
With the EOL of HAProxy 2.4 all supported versions of HAProxy support `haproxy
-cc`, allowing us to unify the tests in this regard.
2026-07-08 14:50:25 +02:00
Tim Duesterhus
da4b7df81d REGTESTS: Remove unused _version function from scripts/run-regtests.sh
This function is no longer used since the support for `REQUIRE_VERSION` was
removed in 8ee8b8a04d.
2026-07-08 14:50:25 +02:00
Tim Duesterhus
788219f905 REGTESTS: Remove unused add_range_to_test_list function from scripts/run-regtests.sh
This function is unused since dc1a3bd999 which
removed the `LEVEL` option.
2026-07-08 14:50:25 +02:00
Remi Tricot-Le Breton
e090316735 MINOR: ocsp: Do not see ocsp loading failures as fatal anymore
Until now, if the call to 'ssl_sock_load_ocsp' raised an error, because
of a missing issuer certificate for instance, we would send a fatal
error code and the init would fail.
With this patch we only consider this as a non-critical error and we
will send a warning instead. In such a case the OCSP response will not
be loaded and stapling or auto update features will not work for the
certificate.
2026-07-08 14:50:25 +02:00
Willy Tarreau
236bb6d5f9 DOC: readme: add a pointer to haterm/haload docs
People landing on the page seeking for help on how to build haterm/haload
need to have a direct entry on this page, so let's add a line pointing to
both docs.
2026-07-08 14:48:52 +02:00
Willy Tarreau
0ed430864e DOC: haterm: add a missing 'haterm' build target on an example
An example build command line was missing "haterm", resulting in building
haproxy instead of haterm. May be backported to 3.4 though not important.
2026-07-08 14:43:07 +02:00
Willy Tarreau
5a6ce04810 MAJOR: mworker: remove deprecated "master-worker" global keyword
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
This keyword, when alone, was already deprecated in 3.3 and marked
for removal in 3.5. Now using it results in an error message inviting
to start haproxy with -W or -Ws instead.

In addition, "master-worker exit-on-failure", which used to automatically
enable the master-worker mode, now doesn't set it and complains if used
without. -W/-Ws. A special case was made for config checks though (-c).

Also worth noting that "daemon" applies to master as well as max-reloads,
and will soon need to be adjusted.
2026-07-08 11:53:13 +02:00
Christopher Faulet
36243965a0 BUG/MINOR: http-conv: Make url-dec failed if no space for trailing null byte
for url-dec converter, a trailing null byte is added at the end of the input
sample because it is requested by url_decode() function. However, when the
buffer was full, the last byte was crushed by the trailing null byte. In
that case, the last character was lost and not decoded.

Now, the converter just fails by returning 0.

This patch could be backported to all supported versions but it is a very
minor issue.
2026-07-08 08:52:07 +02:00
Christopher Faulet
a608d2cade MINOR: stream: Add be_max_retries/cur_max_retries sample fetch functions
These new samples can be used to get, resepectively, the backend value for
the maximum connection retries and the current value applied to the stream.
They could be used to know if a "set-retries" action was performed.

A dedicated reg-test was also added.
2026-07-08 08:52:07 +02:00
Christopher Faulet
b71d27e253 BUG/MINOR: stream: Fix custom max-retries initialization when setting backend
It is possible to overwrite the configured max-retries value with the
"set-retries" action. However there is an issue with the listeners. When the
backend is set, this value is reset with the backend value. However, when
the backend is unchanged, the custom value must be preserved.

To fix the issue, when the stream is created, we set the max-retries to the
listener value. It remains 0 for pure-frontend. And the backend value is
used only if it is not the same proxy.

The documentation of set-retries action was improved.

This patch should be backported as far as 3.2.
2026-07-08 08:52:07 +02:00
Christopher Faulet
ba6c518ee2 REGTESTS: Improve script testing the set-timeout action
First, instead of using syslog to test the result, http-after-response rules
are now used. It is a bit simple. In addition, all custom timeouts are
tested. Some testcase were useless and were thus removed.

In addition, the script was moved in "reg-tests/http-rules/" directory.
2026-07-08 08:52:07 +02:00
Christopher Faulet
69caa53530 BUG/MINOR: stream: Fix custom timeouts initialization when setting backend
Timeouts may be overwritten via the action "set-timeout". Client timeout is
supported on frontends, connect/queue/tunnel/server timeouts are supported
on backends and tarpit timeout is supported on both.

However, there is an issue with the listener. In that case, the action is
always executed in the frontend context and the backend-side timeouts are
then reset, when the backend is selected. If it is another backend, it is
logical. But most of time, the backend is unchanged. And in that case, these
timeouts must be preserved.

Documentation of set-timeout action was improved.

This patch should be backported to all supported versions. Be carefull, all
timeouts are not supported in 3.3 and lower (3.3->3.0: server/tunnel/client
- 2.8->2.6: server/tunnel)
2026-07-08 08:52:07 +02:00
Christopher Faulet
e9a4746f58 MEDIUM: htx: Be sure size of headers never exceed regular buffer on update
When an HTX header or an HTX start-line is added or updated, a test is now
performed to be sure the whole headers size, including the start-line never
exceeds the regular buffer size. It is mandatory because it is now possible
to use larger buffers. However, it remains impossible to send HEADERS frame
in H2 and QUIC larger than a regular buffer. So we must be sure the HTTP
analysis will never produce too big headers because they will be rejected
later, at the forwarding stage. The purpose of large buffers is to be able
to store large payload, larger than regular buffers. Headers must remain
quite small.

This commit depends on the following one:

  MINOR: htx: Add a field to save the headers data size

Both should be backported to 3.4 to avoid any trouble with large buffers. It
is not strictly speaking a bug, but it will avoid hazardous behavior
depending on the payload size.
2026-07-08 08:52:06 +02:00
Christopher Faulet
baec1a9c37 MINOR: htx: Add a field to save the headers data size
The size of headers present in an HTX message are now counted. A dedicated
field was added in the HTX structure to do so. This patch is quite simple
but it will be mandatory to be able to perform some tests on headers. One of
them is to be sure headers never exceed the regular buffer size, even when a
large buffer is used.
2026-07-08 08:52:06 +02:00
Christopher Faulet
ac37158a6d BUG/MEDIUM: chunk: Review chunks usage to not retrieve a large buffer by error
All calls to get_trash_chunk_sz() and alloc_trash_chunk_sz() were reviewed
to be sure we never retrieve a large chunk when it is not expected. Some
calls were not upgrade, but several calls now rely on get_best_trash_chunk()
and alloc_best_trash_chunk() functions.

The idea of the fix is to get a large buffer only when the original buffer
is already a large buffer and the expected size of data exceeds the size of
a regular buffer. This should prevent unexpected memory usage.

This commit relies on the previous one:

  MINOR: chunk: Add function to get a large/regular chunk depending on a buffer

Both must be backported to 3.4.
2026-07-08 08:52:06 +02:00
Christopher Faulet
2b993de2da MINOR: chunks: Add function to get a large/regular chunk depending on a buffer
get_best_trash_chunk() function was added to be able to get a large or a
regular chunk depending on a given size but never larget than a given
buffer. It will be usefull in a futur fix, to prevent unexpected large chunk
usage.

alloc_best_trash_chunk() function is similar but instead of returning one of
the static chunks, it allocate it from the corresponding pool, large or
regular.
2026-07-08 08:52:06 +02:00
Christopher Faulet
ee5cfaf266 BUG/MINOR: sample: Fix a possible underflow on be2hex for large chunk size
For a chunk size exactly equal to bufsize/2, the "max_size" variable could
underflow if a separator is provided. Indeed, "max_size" is first set to
(trash->size - 2 * chunk_size), so to 0. And on the first iteration, the
separator length is removed, making it to underflow.

On 3.3 and lower it is especially an issue with samples larger than
bufsize/2 because data could be written ouside of the buffer. On 3.4 and
3.5, it is not an issue because we fail to retrieve a chunk.

This should be backported to all supported versions.
2026-07-08 08:52:06 +02:00
Christopher Faulet
e528442847 BUG/MEDIUM: tcpcheck: Add proxy used for healthcheck sections in proxies list
The proxy used to parse healthcheck sections was not inserted in the proxies
list. So its initialization was not properly finalized. Among other things,
it was an issue for some arguments, like regular expressions.

This proxy is now inserted in the proxies list.

This patch should fix the issue #3440. It must be backported to 3.4.
2026-07-08 08:52:06 +02:00
Frederic Lecaille
ff95f204ec BUG/MINOR: hbuf: treat unexpected escape sequences as literals
When encountering an unexpected escape sequence, treat it as a literal
character instead of skipping it.

This is a deliberate choice for two reasons:

- to avoid a desynchronization between the h->data counter and the
  buffer content, which would otherwise leave uninitialized memory
  ("garbage") in the destination buffer;

- to ensure that an invalid configuration string triggers a parsing
  error immediately (fail-fast), rather than resulting in a silently
  malformed configuration.

Thank to @dirkmueller for having reported this issue.

No need to backport. huf arrived with this current version.
2026-07-08 08:27:49 +02:00
Frederic Lecaille
1e1e780b79 BUG/MINOR: haload: fix spurious task wakeup in hld_strm_task()
When a stream is freed, hld_strm_task() was unconditionally re-queuing
the user task, even when no streams were left or when no update was
needed. This caused spurious task wakeups, which could incorrectly inflate
connection and request rate counters to come for the -R option implementation.

Only queue the user task if there are remaining streams to process, and
properly update the expiration time.

No need to backport. haload arrived with this current version.
2026-07-08 08:27:28 +02:00
Willy Tarreau
b81e271567 DEV: patchbot: document the shared review persistence
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Update the README to cover the new syncing feature: the deployment
section explains the three optional pieces and their relations. We
also explain the special cases of "Get updates" and "Save updates"
when combined with pending/conflicting changes (particularly in
edition mode).

An explanation of the difficulties to set git permission is also
provided.
2026-07-07 15:05:38 +02:00
Willy Tarreau
78dfc51c95 DEV: patchbot: update: name the touched commits in the storage messages
Every save used to be committed as a bare "update 3.5", which makes the
storage history useless to navigate. The subject now names the branch
and the first commit whose review was touched, followed by "+ N more"
when several, and the body lists all of them one per line:

    update 3.5: 6a7b27a0 + 2 more

    6a7b27a0
    d13aaf05
    b12dd0b5

This is what to grep for when hand-editing the storage repository, for
example to locate the change to revert or rebase. A commit touched by
several directives of the same save (state plus notes) is only listed
once.
2026-07-07 14:45:32 +02:00
Willy Tarreau
75e013727c DEV: patchbot: update: never write to stderr, thttpd sends it first
thttpd forwards the CGI's stderr to the client *ahead* of its stdout:
the socket receives the HTTP status line, then any stderr log line, and
only then the CGI headers, turning the log line into a bogus response
header; on an error path the same mechanism could push garbage in front
of the "Status:" header and corrupt the response entirely.

All diagnostics are already carried by the response itself (die()'s
body, the in-band "warning:" line with git's captured error), so the
duplicated stderr logging brings nothing and only risks breaking the
channel it leaks into: drop it, and state the constraint in a comment
above die() so it doesn't come back. The usage text for a bad command
line is folded into the 500 response body, which is also what a shell
user sees when testing by hand.
2026-07-07 14:45:32 +02:00
Willy Tarreau
d5c6431907 DEV: patchbot: update: report the exact git error to the user
The "git commit failed" warning said nothing about the cause, leaving
the admin to guess between a missing identity, an ownership refusal, a
git binary absent from the restricted PATH the web server gives to its
CGIs, etc. run_git() now captures the command's stdout and stderr
through a pipe and the warning line carries git's own message (first
255 bytes, control chars flattened), so the admin directly sees the
cause; as the command runs through /bin/sh, an unfindable git yields
status 127 and the shell's message, reworded as "cannot execute git:
..." to directly point at the typical PATH issue. Capturing also
guarantees that git output can never corrupt the CGI response nor leak
to the client on servers which wire the CGI's stderr to the socket.
2026-07-07 14:45:32 +02:00
Willy Tarreau
96f415006e DEV: patchbot: update: report git commit failures in the save response
When the git commit fails after a save (typically a missing committer
identity in the storage repository, or an ownership/permission issue),
the failure was only logged to stderr, which lands in the web server's
error log at best: the file kept being updated but the history silently
stopped being recorded. Report it as a "warning: git commit failed ..."
line appended to the response, where the page can show it to the user,
on top of the stderr log. Also stop treating a no-op as a failure:
re-pushing identical content stages nothing, so the commit is now
simply skipped when "git diff --cached --quiet" reports no staged
change, instead of letting "git commit" fail on an empty commit.
2026-07-07 14:45:32 +02:00
Willy Tarreau
bff33b4e32 DEV: patchbot: update: support replacing a whole note blob (setnotes)
Notes are append-only on the wire, which makes concurrent edits
conflict-free but leaves no way to revise or clean up a note from the
page: fixing a note requires a hand-edit of the storage file. This adds
the replacement directive that the design had reserved:

    <cid> setnotes <hash> <replacement text>

Unlike the other directives, a replacement must carry a token of the
base it was computed from, or it could silently destroy a concurrent
update (one reviewer's append landing between another's read and
replace). The token is the SDBM hash (8 hex chars) of the note blob
the client based its edit on: the server only applies the replacement
if it still matches the stored blob (empty-string hash for a commit
without notes). On mismatch the directive is dropped, the line is left
exactly as found, and a "conflict <cid>" line is emitted in the
response before the resulting lines so that the client can point the
user at what needs manual reconciliation; other directives from the
same POST are still applied, and nothing is written nor committed when
everything conflicted. SDBM is trivially computed on both sides (a
concurrency token, not a security feature, and JS crypto is unavailable
on plain http anyway), and its small multiplier keeps the whole hash
computation exact in awk's double-precision arithmetic, which is
precisely why it was chosen over wider-multiplier hashes.

An empty replacement deletes the notes (and the line if no state is
left), finally allowing obsolete notes to be removed without editing
the file by hand. Replacements are capped to 4000 chars instead of the
500-char append cap, since a coalesced blob may legitimately have grown
beyond a single addition.
2026-07-07 14:45:32 +02:00
Willy Tarreau
13d465a61a DEV: patchbot: update: return the stored overlay as JSON on GET
This adds the read side of the review persistence CGI: GET
update.cgi?branch=X.Y now returns the current overlay for that branch
as a JSON array of {"cid", "state", "notes"} objects with absent fields
omitted; a missing or empty file yields "[]". The raw storage format
never travels: the notes are unescaped by the parser and JSON-escaped
on output, so the client can JSON.parse() the response and insert the
notes via textContent directly. Unparseable lines or fields are
silently skipped as everywhere else.

Reads are lockless: the atomic rename on the write side guarantees
that the file is always a complete valid version. The response carries
Cache-Control: no-store so that a browser never reuses a stale overlay
on refresh.
2026-07-07 14:45:32 +02:00
Willy Tarreau
b232b554f2 DEV: patchbot: update: add an awk backend to persist review edits
The backport review page keeps the human edits (verdict overrides and
notes) only in the loaded DOM: they are lost on reload and never shared
between reviewers. This adds the server side of the shared persistence
design: update.awk, a GNU awk CGI script which stores these edits into
one file per major branch (e.g. "3.5") inside a dedicated git
repository, one line per touched commit:

    <commit_id> [state <n|u|w|y>] [notes "<quoted notes>"]

The overlay only ever stores human edits keyed on the commit id; the AI
verdict and explanation stay in the generated HTML. Commit ids are
length-agnostic and matched by symmetric prefix (first match wins), so
the current 8-char pipeline and a future 12-char one both work without
any migration.

POST applies line-oriented directives ("<cid> state <n|u|w|y|revert>",
"<cid> notes <text to append>"), none of which carries a base value:
states are last-write-wins and notes are append-only (capped to 500
chars per push and sanitised so that no newline may ever enter a stored
line), which keeps concurrent edits conflict-free. Broken directives,
fields or lines are silently ignored, never fatal, and lines not being
modified are preserved byte-for-byte so that admin hand-edits survive.
Writers are serialised by a mkdir lock at an obvious place (<repo>/lock)
with PID-gated crash takeover via atomic rename, the file itself is
replaced by an atomic rename from a temp file inside the lock dir, and
every resulting state is committed to git, which acts as the event log
(git blame/log -L provide the full history). A crash at any point leaves
at worst a stale lock (reclaimed on the next write) or a valid but
uncommitted tree (folded into the next commit), never a broken file.

The takeover races are covered: the staleness decision and the takeover
rename are not one atomic operation, so the thief verifies after the
rename, discarding the stolen dir only if it still carries the pid that
was judged dead and renaming it back in place untouched otherwise; the
victim redoes the whole locked cycle from a fresh read when its final
rename fails, since nothing was applied to the branch file yet; a writer
finding its own pid in the lock adopts it as stale; and the release only
removes the lock after checking that it still contains our own pid.

A few awk specifics are worth noting: external commands (git, mkdir,
mv) go through /bin/sh, so everything interpolated into a command line
is shell-quoted (single-quote is escaped and the argument placed inside
single quotes); the -b (bytes) flag keeps all string operations byte-
based regardless of the locale; and a manual argv parsing due to gawk
silently consumes a leading "-r" argument, ignoring ours.

Also note that gawk uses a file cache for getline() and co, which opens
lots of traps so we need to be extremely careful about properly closing
files if we want to check for changes (e.g. lock's pid file).

Finally, writing through a redirection whose target cannot be opened is
a fatal awk error terminating the script without even a response, so the
writes into the (stealable) lock dir are arranged to resist a takeover:
the pid is written through the shell, where a vanished dir is a plain
command failure, and the temp file is opened the very instant the lock
is acquired, its descriptor surviving a later theft. The update.cgi
wrapper considers any >0 return code as a failure and returns a generic
error as it will indicate that the awk script itself couldn't produce
a valid response.

For now, only the POST ("save changes") action is implemented.
2026-07-07 14:45:32 +02:00
Willy Tarreau
f245b15834 DEV: patchbot: repeat the syncing buttons at the bottom of the page
The "Get updates" and "Save changes" buttons only existed at the top
right of the page, while a review session ends at the bottom of the
table: with no button left in sight there, it was way too easy to
forget to save the work. Emit a copy of both buttons and of the status
line at the bottom right, sharing the same handlers; the status
message and the save-button graying are applied to both instances at
once so the two spots always tell the same story.
2026-07-07 14:45:32 +02:00
Willy Tarreau
09814345d5 DEV: patchbot: tolerate polluted save responses and show server warnings
Some server setups leak the CGI's stderr into the response body (e.g.
inetd-style servers where fd 2 is the client socket): since stderr is
unbuffered and stdout is buffered, a git error message then lands
*before* the "OK <n>" line, and the applied-count check failed with a
cryptic "server applied only ? of N changes" although everything had
been applied. Scan the whole response for the lines of interest (the
"OK <n>" count, the "conflict" and "warning" lines) instead of assuming
they come first, dump the raw response to the console when no count is
found at all to ease diagnosis, and display the server's warning lines
(such as the new "git commit failed" one) next to the save status so
that a recording problem is visible from the page instead of being
buried in a server log.
2026-07-07 14:45:32 +02:00
Willy Tarreau
351c7e6598 DEV: patchbot: don't pretend a save succeeded when the server ignored it
The save handler treated any HTTP 200 as a full success and advanced
the local reference for everything it had sent, only special-casing the
reported conflicts. But the server legitimately drops directives it
cannot parse, and answers "OK <n> directives applied" with what it
really did. The typical case is an outdated update scripton the server
which ignores the whole "setnotes" directive, applies nothing, and
the client still displayed the edit as saved... until the next "Get
updates" reverted it.

Let's count the directives sent, and when the server's applied count
plus the reported conflicts don't add up, believe the server, not
ourselves: advance nothing, keep every edit local (boxes open, save
button lit) and tell the user how many changes were ignored,
suggesting a version mismatch.
2026-07-07 14:45:32 +02:00
Willy Tarreau
f583269db9 DEV: patchbot: gray the save button when there is nothing to save
The "Save changes" button used to remain active all the time, giving no
hint about whether anything was pending. It is now disabled whenever
nothing differs from the reference: no verdict change, no non-empty
note addition, no note edition differing from its base. It gets
re-evaluated after every action which may change that (verdict clicks,
typing in a note input, opening/cancelling a box, updates and saves),
bailing out at the first pending change so the common case stays cheap.

As a side effect, the button lighting up right after a reload confirms
at a glance that the browser restored unsaved local edits.
2026-07-07 14:45:32 +02:00
Willy Tarreau
a6906d799f DEV: patchbot: let the page edit and delete whole notes
An "[edit note]" link now appears next to "[add note]" whenever a line
has shared notes: it presents the whole note blob in the input box for
edition, and the save sends it as a replacement (the "setnotes"
directive, carrying the hash of the blob the edit was based on).
Emptying the box deletes the note. Clicking "add note" first and then
"edit note" merges the reference notes with the pending addition so
nothing typed so far is lost, and a "[cancel]" link aborts an edition
opened by mistake without touching anything.

An open input only disappears once its content is synchronized with the
reference: a successful save, an update proving an exact match, or an
explicit cancel. To that end, "Get updates" first silently closes the
no-op boxes (opened but nothing changed), then after applying the
fetched state it closes the boxes it made redundant (an addition
someone already pushed, an edition matching the current notes), and
re-bases an edition whose base moved, turning it red: red uniformly
means "the reference changed under your edit, review the notes above
against your text before saving". A save refused by the server (the
"conflict <cid>" response lines) turns the input red the same way,
keeping it in edition; states and additions from the same save are
unaffected. After a reload, a browser-restored pending note reopens in
append mode, the only safe assumption since a lost replacement base
cannot be recovered.
2026-07-07 14:45:32 +02:00
Willy Tarreau
0ed6d1f840 DEV: patchbot: let the page save the review edits to the server
This is the write side of the review syncing: a "Save changes" button
next to "Get updates" collects the local edits and pushes them to
update.cgi. An edit is a radio button change differing from the
reference state (so clicking around and coming back to the reference
sends nothing), or a note typed in the per-line input revealed by the
new "[add note]" link under the AI explanation (500 chars max, matching
the server-side cap).

No directive carries a base value: states are last-write-wins and notes
are append-only server-side, so two reviewers saving concurrently
cannot conflict. On success the reference advances to the pushed values
and the note inputs are cleared, so the page is clean without needing a
refetch; on error (server busy or unreachable) everything stays local
and a later click simply retries.
2026-07-07 14:45:19 +02:00
Willy Tarreau
635d91aad9 DEV: patchbot: let the page fetch the shared review state
This adds the read side of the review syncing to the generated page: a
"Get updates" button at the top right retrieves the shared state from
update.cgi (reached by a bare relative URL, so it must be in a cgi-bin
directory next to the page) and applies it. Nothing is fetched
automatically, not even at load time: it's up to the user to explicitly
click to resynchronize, and without it (or with the server down) the
page keeps behaving fully standalone as today.

Three states exist per line to make this work. The original state is
the verdict the bot chose, captured at load time and constant. The
reference state is the last known shared state, on top of which the
user's edits sit; it starts equal to the original. The local state is
the DOM itself (the checked radios). Applying a fetched overlay
recomputes every line's reference as "the server's entry if any,
otherwise the bot's verdict", so a removed override properly falls back
to the original; the reference always advances but the displayed state
only moves where the user had no local edit: local edits win, and
re-applying the same overlay twice changes nothing.

The received commit ids are resolved exactly first, then by symmetric
prefix (one id being a prefix of the other, for mixed-length ids), first
line wins. The shared notes land in a dedicated container below the AI
explanation, rendered via innerText (no HTML injection) and replaced
wholesale so the operation stays idempotent; entries for commits absent
from the page are simply ignored.

The whole exchange was tested with the real generated page's scripts
running against a stubbed DOM covering load capture, prefix resolution,
adopt-vs-keep on both changed and disappeared entries, idempotent
re-application, and silent degradation on fetch failure.
2026-07-07 14:43:28 +02:00
Willy Tarreau
fcbea9e0d0 DEV: patchbot: pass the branch version to the generated page
The review page will need to exchange its state with the update.cgi
service sitting next to it, and for this it must know which branch it
covers since all branches' pages may share a directory. post-ai.sh
takes a new "-v <version>" argument and emits it as a "branch" JS
variable; when absent the variable is empty and the page will simply
not offer syncing, keeping the output standalone as today.

update-3.0.sh deduces the version from its own name (update-3.0.sh ->
3.0), so that adding a symlink with another version for a new branch
continues to work with no other change, and passes it to post-ai.sh.
2026-07-07 10:58:38 +02:00
Willy Tarreau
e4e99282ca DEV: patchbot: only display the first 8 chars of the commit id
The commit id column doesn't need to show more than 8 chars to stay
unambiguous within a single page, and longer ids needlessly widen the
table. Everything that is keyed on the id (the commit link, the row's
name= attribute and the cid[] JS array) keeps the full id produced by
the pipeline, whatever its length, so this is a display-only change
which also gets us ready for a possible future move of the pipeline to
longer ids.
2026-07-07 10:58:38 +02:00
Willy Tarreau
b39d2afe48 DEV: patchbot: keep the review start in sync with the radios on reload
When the page is reloaded, the browser restores the "review" radio
column to the user's last selection (e.g. "All"), but the "review" JS
variable is regenerated to the default first line to review: the
listing then restarts from that line while "All" still appears
selected, and one has to click a random line then "All" again to
really see everything.

Give an id to each line's review radio and resynchronize the variable
from the actually checked radio when the page loads: a restored "All"
(or any restored line) now behaves as selected, and on a fresh load the
checked radio is the generated one so nothing changes.
2026-07-07 10:58:38 +02:00
Amaury Denoyelle
3596904a8d MEDIUM: quic: remove deprecated keywords
Some checks failed
FreeBSD / clang (push) Waiting to run
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Several QUIC related keywords were removed in 3.4. The legacy options
were marked as deprecated and scheduled for removal in 3.5. This patch
applies this removal for the upcoming 3.5.
2026-07-06 17:19:24 +02:00
Olivier Houchard
753aa44416 BUG/MEDIUM: fd: Fix a deadlock when closing other tgroups fds
Commit 061754b249 attempted to make it
possible to close file descriptors belonging to other thread groups by
using thread isolation.
The problem is, closing other thread groups' fds usually happens when we
destroy a listener, in which case we hold the listener lock. If any
other thread tries to get that lock while we're waiting for the thread
isolation, then they will deadlock.
This can happen with any type of listener, but it is easier to reproduce
with a suspend/resume loop with ABNS sockets, as a suspend translates to
a close here.
To fix that, instead of using thread isolation, do something similar to
what's done when the fd belongs to our thread group. Increase the tgid
ref counter, so that we're sure nobody will close the fd while we're
dealing with it, then set the FD_MUST_CLOSE bit and set thread_mask to
0.
At this point, if no thread was running on that fd, no one will and we
can safely close it. So just call _fd_delete_orphan() if the
running_mask is 0 and if the FD_MUST_CLOSE bit is still there, otherwise
we can safely assume another thread will take care of it.

This should be backported up to 2.8.
2026-07-06 16:03:58 +02:00
Amaury Denoyelle
73a4f6bea7 BUG/MINOR: quic: ignore STREAM after MUX closure on BE side
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
For frontend connections, quic_conn layer is able to reject any new
streams opened after MUX closure. This is necessary as the peer may not
have been notified yet of the closure.

This operation is unnecessary on backend side. This is due to the fact
that only HTTP protocols are currently supported on top of QUIC, with
requests initiated by the client. For requests started before the MUX
closure, either they are already completed or closed early with a
STOP_SENDING emitted during stream shut.

Prior to this patch, spurrious RESET_STREAM could have been emitted on
backend connections after MUX closure as quic_conn stream_max_bidi was
not correctly set. Now reject is only performed for frontend connections
so this should not occured anymore.

This should be backported up to 3.3.
2026-07-06 10:19:12 +02:00
Amaury Denoyelle
bf76275fff BUG/MEDIUM: mux_quic: complete stream shutdown for read channel
Prior to this patch, shut stream callback only handles write channel
closure. In case of an early closure, a RESET_STREAM would be emitted.

On the frontend side in most cases this is sufficient as read channel is
already closed, as HTTP/3 GET requests has been fully received. However,
this may not be the case for POST requests. Also, on the backend side,
haproxy acts a client. In this case, a stream early closure will
typically happen before receiving the full response. Nothing will be
emitted (RESET_STREAM is unnecessary as write channel is already
closed), thus the server peer will continue to emit.

To fix this situation, the current patch implement read channel closure
on shut if SE_SHR_RESET is set. Callback lclose from app_ops is called
with a new dedicated mode for read channel closure, which will result in
a STOP_SENDING frame generated by H3 and hq transcoders. This will
instruct the peer to stop emission.

This should be backported up to 3.3. Note that this depends on the
following patch :
  dde3ee06c30f20091443bdafdda0e0294f7ac26b
  MINOR: mux_quic: use separate error code for STOP_SENDING
2026-07-06 10:19:12 +02:00
Amaury Denoyelle
cc56803799 MINOR: mux_quic: adjust shut stream callback
Clean up MUX QUIC stream shut callback with a central invokation for
lclose app_ops. Also, traces are added at H3 layer to log the method of
closure.
2026-07-06 10:19:12 +02:00
Amaury Denoyelle
f2014ec474 MINOR: mux_quic: use separate error code for STOP_SENDING
Prior to this patch, a single error code was registrable at the QCS
level. This code was used both for RESET_STREAM and STOP_SENDING
emission. It was specified via qcc_reset_stream().

This patch extends the API so that now a dedicated error code is
implemented for STOP_SENDING as well. This may be necessary as both
frames can be sent in different context, with a diverging error code.

This patch is required to implement STOP_SENDING emission during shut
callback when read channel is closed.
2026-07-06 10:19:12 +02:00
Amaury Denoyelle
c0c3a325a4 BUG/MEDIUM: mux_quic: do not free QCS if STOP_SENDING to sent
When stream detach callback is called, the default behavior is to free
the associated QCS instance. However, QCS may be preserved in so-called
detached state if there is remaining data to sent.

This condition is checked via qcs_is_close_local() which ensures that
either FIN or a RESET_STREAM was emitted. However, this does not take
into account a scheduled STOP_SENDING emission, which can happen in case
of request abort for example.

Adjusts qcm_strm_detach() to also take into account STOP_SENDING
emission before freeing or keeping a detached QCS instance. As a
complement, QCS have to be purged after STOP_SENDING emission when
reaching completion.

On frontend side, this bug is probably only visible in case of HTTP/3
POST. When dealing with GET, FIN is most of the time received earlier,
which render STOP_SENDING unnecessary. This issue however has a bigger
impact on the backend side. In case of stream abort, for example on
timeout, the server may be left unnotified and will continue to emit
STREAM data despite QCS closure on haproxy client side.

Note that this fix also has a side effect on backend connection reuse.
Indeed it may increase the rate of QCS in detached state. This may
prevent an idle connection to be reinserted in the server pool, without
any possibility to reinsert it later. In the end this causes a lower
reuse rate. This is an issue which must be addressed in a dedicated
patch. For now, add a COUNT_IF_HOT() to report when such situation
occurs.

This should be backported to all stable releases, after a period of
observation. COUNT_IF_HOT() is unnecessary on 3.2 and below.
2026-07-06 10:19:12 +02:00
Willy Tarreau
7dfe7d240e BUILD: makefile: add a new generic target "tiny"
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
FreeBSD / clang (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
This target disables all possible features except poll(). It is meant to
serve as a base for small embedded setups, on top of which one may manually
enable select features. Even threads, traces/h2/fcgi/SPOE are disabled.
The default executable is roughly 80% smaller than with linux-glibc:

  $ size haproxy-linux-glibc haproxy-tiny
     text    data     bss      dec    hex filename
  3660924  176964 9868784 13706672 d125b0 haproxy-linux-glibc
  2537864  146512   84928  2769304 2a4198 haproxy-tiny

With SSL enabled, the difference shrinks a bit (-77%):

  $ size haproxy-linux-glibc-ssl haproxy-tiny-ssl
     text    data     bss      dec    hex filename
  4163373  208788 9873904 14246065 d960b1 haproxy-linux-glibc-ssl
  2950852  177732   90048  3218632 311cc8 haproxy-tiny-ssl
2026-07-03 16:33:27 +02:00
Willy Tarreau
0bf32cee66 BUILD: makefile: add an option to enable or disable SPOE (USE_SPOE)
USE_SPOE is enabled by default and allows to disable SPOE when forced to
zero. It saves roughly 92kB on the executable.
2026-07-03 16:33:27 +02:00
Willy Tarreau
3939dc0577 BUILD: makefile: add an option to enable or disable FCGI (USE_FCGI)
USE_FCGI is enabled by default and allows to disable FCGI when forced
to zero. It saves roughly 75kB on the executable.
2026-07-03 16:33:27 +02:00
Willy Tarreau
382a495649 BUILD: makefile: add an option to enable or disable HTTP/2 (USE_H2)
USE_H2 is enabled by default and allows to disable HTTP/2 when forced to
zero. It saves roughly 127kB on the executable.
2026-07-03 16:33:27 +02:00
Willy Tarreau
d92c997c68 BUILD: makefile: add macros enable_opts and disable_opts
These ones are used to only enable or disable selected options.
2026-07-03 16:33:27 +02:00
Willy Tarreau
1a4be29252 BUILD: makefile: only build trace.c and ssl_trace.c when USE_TRACE is set
There's no point in building these ones anymore when traces are disabled,
nothing relies on them. This brings extra 28kB savings, resulting in 709kB
total savings when disabling traces.
2026-07-03 16:33:24 +02:00
Willy Tarreau
75163e93a8 CLEANUP: trace/tree-wide: drop trace decoding/definition when USE_TRACE=0
The various trace sources always have the same pattern:
  - trace events
  - trace source
  - trace decoding function

Dropping these when USE_TRACE=0 definitely makes sense. There are two
modes of definition here:
  - those designed after mux_h2 which interleave #define and the entry
    definition in the event. These ones cannot be removed without a
    significant code move to split the #define and usage apart. Instead
    here we mark the struct __maybe_unused, so that the compiler will
    just not implement it.

  - those designed like stream.c where defines are separated. Here we
    can simply enclose the events definition inside the USE_TRACE guard

For most of these the static declaration of the trace function was moved
after the events so that the #if defined(USE_TRACE) could be placed between
the two. Nothing else was changed.

This saves another 51 kB of object code when USE_TRACE=0.
2026-07-03 16:32:28 +02:00
Willy Tarreau
5dff525422 CLEANUP: trace/config: do not register section "traces" with USE_TRACE=0
We don't want to register the "traces" section when traces are disabled.
Also this forces us to continue to build trace.c.
2026-07-03 16:32:28 +02:00
Willy Tarreau
6ea18e0c74 CLEANUP: trace/h3: allow to disable traces in H3
It requires essentially a few ifdefs and to add a dummy definition of
h3_trace_header() to completely disable traces in H3. This reduces the
object code by 35 kB.
2026-07-03 16:32:28 +02:00
Willy Tarreau
8ac1e3ff5b BUG/MINOR: trace/quic_frame: use buf, not trace_buf in chunk_frm_appendf()
The function takes a buffer in argument which is the target buffer. The
first calls properly use it but the subsequent ones, probably due to
reused/moved code, directly write into &trace_buf, thus ignoring the
buf argument. Fortunately all call places pass &trace_buf for buf, so
it currently has no impact but could possibly change.

No backport is needed, but it doesn't hurt to backport it if it helps.
2026-07-03 16:32:28 +02:00
Willy Tarreau
19d48c3d34 CLEANUP: debug/trace: remote "debug dev trace" when USE_TRACE is not set
The functions associated with this command were already subject to
DEBUG_DEV, let's also add USE_TRACE for them to be defined. This saves
8kB.
2026-07-03 16:32:28 +02:00
Willy Tarreau
10166c16ad CLEANUP: mux-h2/traces: remove unused trace code when building without USE_TRACE
By just moving a few definitions, creating two dummy inline functions and
a few ifdefs, we can get rid of the entire trace generation code in the
H2 mux and save ~96 kB. This is what this patch does. Even the trace_h2
struct is removed in this case.
2026-07-03 16:32:28 +02:00
Willy Tarreau
8bb55185f6 CLEANUP: haproxy: remove -dt parsing and help when !USE_TRACE
Better not show -dt in the help message and stop parsing it when
USE_TRACE is disabled since traces won't work anyway.
2026-07-03 16:32:28 +02:00
Willy Tarreau
fa8ca0be4d BUILD: ssl: avoid a wrong null deref warning in ssl_sock_handshake
When disabling traces, "conn" isn't used between ctx assignment and its
first usage, and as usual, gcc wrongly believes that a null check in a
shared function implies the checked argument may be NULL where it's used,
leading to this warning:

  src/ssl_sock.c: In function 'ssl_sock_handshake.constprop':
  src/ssl_sock.c:6049:7: warning: null pointer dereference [-Wnull-dereference]

Assigning ctx after the conn_ctrl_ready() check is sufficient to shut it
up, so let's do this. It should also result in slightly better code.
2026-07-03 16:32:28 +02:00
Willy Tarreau
b17a16b3e2 MINOR: trace: always pretend to use args when disabled
When traces are disabled, we used to make TRACE() and other macros just
emit a "do { } while (0)" statement, which has the unfortunate limitation
of explicitly marking the arguments as not used. As such, all variables
that are initialized in functions for the sole purpose of being passed
to the trace calls end up emitting warnings about "foo defined but not
used". It is difficult to keep these in a clean state all the time, and
to always think about adding __maybe_unused after each declaration, and
the traces try hard to be developer-friendly in order to gain in adoption.

Let's just remap all macros to __eat_all_args() which will mark all
arguments as used. No code is emitted, the output binary is the same
as with the while(0) stuff, but syntactically speaking the argument is
used and the compiler is happy.

It may be useful to backport this to 3.4 as it's already expected that
some future fixes will trigger build warnings there otherwise. This
commit requires these two ones:

  CLEANUP: traces: get rid of a few rare empty args in TRACE calls
  MINOR: compiler: add a macro to ignore all arguments
2026-07-03 16:32:28 +02:00
Willy Tarreau
7fc3a52aaf MINOR: compiler: add a macro to ignore all arguments
Regularly when disabling features (e.g. traces), some macros that would
make use of some arguments end up not consuming them at all, making the
compiler complain that "variable foo defined but not used".

An elegant way to generically mark arguments as used is to pass them to
a variadic function. However a first argument is needed. So we create a
macro that passes (0, __VA_ARGS__) to an inline function that does nothing
from its arguments, and that's done.
2026-07-03 16:32:28 +02:00
Willy Tarreau
50c00ee235 CLEANUP: traces: get rid of a few rare empty args in TRACE calls
The TRACE macro allows to leave empty args and automatically turns them
into zeroes. However it also limits how we can remap the macro, because
functions do not accept this for example. There are very few places all
over the code where ',,' exists in TRACE calls, so let's explicitly add
the 0 there. It could even make some editors' syntax highlighting happier.
2026-07-03 16:32:28 +02:00
Willy Tarreau
4fe06d8c83 BUILD: quic: workaround a gcc bug saying "maybe used uninitialized" when USE_TRACE=0
In quic_transport_params_store(), we call qc_early_transport_params_cpy()
if edata_accepted is set, which copies one by one all tx_params into the
locally allocated etps struct, and later after updates we call
qc_early_transport_params_validate() to check if they changed. It turns
out that when USE_TRACE is disabled, gcc 4 to 13 are confused and believe
that one or several of the fields compared in the later function might be
used uninitialized. A careful code inspection proves that this is not the
case. Setting them to zero in the _cpy() function makes the warning
disappear, it's really an issue related to variable propagation it seems,
which can explain why it doesn't happen with traces (code is a bit more
complex). Gcc-13 only emits a warning about a single field, and gcc-14
completely solved it. Playing with consts, __maybe_unused etc has no
effect.

One thing works however, it is to mark the _validate() function noinline.
In this case it is implemented normally and the compiler doesn't put its
nose into the propagation path and doesn't complain.

Such comments are always scary because one may seriously wonder whether
the compiler emits valid code when it says this...

It should be backported to 3.4 which experiences the same warning with
USE_TRACE=0.
2026-07-03 16:32:28 +02:00
Amaury Denoyelle
364d16030a BUG/MINOR: mux_quic: prevent multiple STOP_SENDING emission per stream
Some checks failed
FreeBSD / clang (push) Waiting to run
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
A QUIC stream may be aborted to ignore future data read. This also
prepares a STOP_SENDING frame to instruct the peer to close its write
channel.

This capability is exposed via qcc_abort_stream_read() which should be
guarded against multiple invokation for a single stream. This was
checked via QC_SF_TO_STOP_SENDING flag. However, this flag is resetted
once STOP_SENDING frame is emitted. Thus in theory it could be possible
to emit several STOP_SENDING for a single stream.

This patch improves this by using QC_SF_READ_ABORTED flag check. This
flag is set during qcc_abort_stream_read() and never removed even after
STOP_SENDING frame is emitted.

This bug was never encountered in a real situation. However, this patch
is necessary to definitely guarantee that it cannot occur.

This should be backported up to 2.8.
2026-07-03 09:27:21 +02:00
Amaury Denoyelle
7f416252b5 BUG/MEDIUM: h3: fix parser desync on error with multiple frames
On success, h3_rcv_buf() returns the number of parsed STREAM bytes which
are removed by the caller afterwards. A success value is mandatory so
that the underlying QUIC packet is acknowledged.

When H3 parser detects an error during HEADERS or DATA parsing, the
stream or the connetcion is flagged for closure. If there is remaining
frames, they are simply ignored and h3_rcv_buf() returns the remaining
input buffer size.

However, this value is wrong in case one or several frames were already
parsed before the invalid frame in the same h3_rcv_buf() invokation.
This instructs caller to only remove a subset of the data and parsing is
restarted on a random boundary. Most of the times this generates again a
new final yet invalid error, possibly overwriting a stream error with a
full connection closure.

This patch fixes the return value in case of an error during HEADERS or
DATA parsing by ensuring that total variable is always incremented
instead of being directly assigned.

This must be backported up to 2.8.
2026-07-03 08:12:00 +02:00
Christopher Faulet
f3411cb734 BUG/MINOR: http-htx: Don't by-pass HTX API when merging cookie values
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
http_cookie_merge() function is responsible to add a cookie header and merge
all values from a list. However, it was performed by appending values by
hand, using the pointer to the header value and changing the block
length. This was totally by-passing the HTX API.

For now, there is now bug because the function is called by h2 and quic
muxes when a HTTP message is parsed. And the cookie header is the last one
inserted. The HTX message is never fragmented and data from other blocs
cannot be overwritten. But, it could be an issue if it is called in another
context, from the HTTP analysis for instance.

To fix the issue, the function now relies on htx_replace_blk_value()
function to add the value separator first and then a cookie value.

This patch must be backported as far as 2.6.
2026-07-02 17:03:54 +02:00
Christopher Faulet
e237b4ff9f BUG/MINOR: mux-quic: Fix handling EOM after in qcs_http_rcv_buf()
In qcs_http_rcv_buf() function, when the buffers cannot be swapped and
htx_xfer() function is called, the way the EOM flag is handled is buggy. The
htx_xfer() function is responsible to tranfer HTX flags from the QCS message
to the CS one. And when it is performed, HTX flags of the original message
are reset.

So, the following test on the EOM flag when the QCS message is empty is
never true. Because of this bug, QC_SF_EOI_SUSPENDED flag is never tested on
this code path and <fin> variable is not set to 1 as expected.

To fix the issue, we must test the EOM flag on the CS message.

This patch must be backported to 3.4.
2026-07-02 17:03:54 +02:00
Christopher Faulet
9aa081b17e CLEANUP: applet/http-client: Don't needlessly copy HTX flags after htx_xfer()
htx_xfer() function already takes care to copy HTX flags (EOM and
errors). So it is useless to do so in caller functions.
2026-07-02 17:03:54 +02:00
Christopher Faulet
80e0004c91 BUG/MAJOR: htx: Don't swap buffers for empty HTX message with an error
Recent fix of some HTX muxes to drain remaining data when the stream is in
closed state revealed a bug, mainly due to a corner case of the HTX API.

It is possible to have an empty HTX message with a parsing/internal
error. In that case, the underlying buffer remains full. It is mandatory to
prevent any buffer release and be sure the error will be handeled.

On the other end, at several places, when data must be transfer from an HTX
message to another one, we try to swap underlying buffers instead of
performing a bloc-per-bloc copy. To do so, we rely on b_xfer() function. One
condition is that the destination message must be empty. And here is the
issue. The HTX message can be empty but the buffer can also be full because
an HTX error was triggered earlier and not handled yet. In that case,
attempting to call b_xfer() leads to a crash because the destination buffer
is full. It is not expected to call b_xfer() if there is not enough space in
the destination buffer.

So, it appears the HTX API should be improved/fixed but first of all, the
bug must be fixed. Especially because stable versions are also affected. The
htx_is_empty_noerr() function was added to know if a HTX message is empty
and no error was reported on it. And this function is now used, instead of
htx_is_empty(), to know if we can safely swap the underlying buffers or not.

the FCGI, H2 and QUIC multiplexers are concerned. The HTTP client and the
applet API were also fixed while it seems harder to trigger the bug at these
places.

The fix must be backported to all supported versions.
2026-07-02 17:03:54 +02:00
Huangbin Zhan
c6d71297bc BUG/MINOR: tools: fix invalid character detection in strl2ic()
ASCII characters with a value smaller than '0' were not properly
detected as invalid characters, leading to incorrect behavior. The
strl2irc() and strl2llrc() functions are not impacted because this
situation is detected by their overflow checks.

Fixes Github issue #3357.
2026-07-02 16:02:49 +02:00
Frederic Lecaille
e66c2c3acd BUG/MINOR: init: fix default global settings being overwritten by -G
The default global configuration tuning settings (tune.memory.hot-size,
expose-experimental-directives, and tune.pipesize) were lost when the
-G option was used.

The bug was caused by an incorrect scope: these default settings were
nested inside the block that generates the default global header, which
is skipped/overwritten when -G is provided. Fix this by closing the
conditional block early.

This patch depends on this commit:
    "01f4e33ea MINOR: hbuf: new lightweight hbuf API"

Must be backported to 3.4.
2026-07-02 13:55:49 +02:00
Dragan Dosen
146015e58b MINOR: log: add app_log_raw() and send_log_raw() for binary-safe logging
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
app_log() and send_log() build the message with vsnprintf(), which stops
at the first NUL byte and therefore cannot emit an arbitrary binary
payload.

Add two variants that pass a pre-built <msg> of <len> bytes straight to
__send_log() without formatting it, so embedded NUL bytes are preserved:

  * app_log_raw()  : takes an explicit list of loggers and a tag
  * send_log_raw() : derives both from a proxy

The send path still strips trailing LF / NUL bytes (kept for the legacy
text logs), so the message must be self-terminating by its own encoding
and must not rely on a meaningful trailing '\n' or NUL.
2026-07-01 23:51:17 +02:00
Frederic Lecaille
3ef29b4314 MINOR: haload: import source code and documentation
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
This patch imports the implementation of haload, a lightweight,
multi-threaded traffic generator designed to benchmark HTTP infrastructures
under heavy loads. Built onto HAProxy's highly scalable
architecture, it natively supports HTTP/1, HTTP/2, and HTTP/3 (QUIC).

It uses the previously exposed initialization functions, the no-listener mode,
the lightweight hbuf API, and the specialized hldstream object types to
dynamically derive and generate its configuration in memory from basic
command-line inputs. By leveraging HAProxy's internal HTX
(Internal HTTP Native Representation) format, haload abstractly manipulates
HTTP elements independently of the wire protocol. This
abstraction allows it to generate unified requests and process responses
seamlessly across HTTP/1.1, HTTP/2, or HTTP/3 without duplicating the payload
handling logic for each version.

 - Makefile:
   Introduce the 'haload' compilation target and define HALOAD_OBJS.

 - src/haload.c, include/haproxy/haload.h:
   Add user and stream task scheduling handlers, HTX-driven traffic orchestration
   mechanisms, and terminal benchmarking statistical summary rendering.

 - src/haload_init.c:
   Implement program arguments parsing, fileless HAProxy memory configuration
   generation, and target URL allocations.

 - src/stconn.c:
   Wire up sc_attach_mux() to properly allocate the specific tasklet
   context when dealing with a haload stream.

 - doc/haload.txt:
   Add detailed documentation covering compilation, flags, and usage examples.
2026-07-01 15:22:14 +02:00
Frederic Lecaille
330d60020d MINOR: server: export functions used during server initialization
Export _srv_parse_kw() and srv_postinit() so they can be called from
haload (to come), which needs to configure servers using HAProxy's configuration
parser keywords.
2026-07-01 15:22:14 +02:00
Frederic Lecaille
b3ed9c1879 MINOR: stconn: export sc_new()
This patch exports sc_new() by removing its static storage class and
adding its prototype to include/haproxy/stconn.h.

This is required to allow external modules, such as the upcoming haload
benchmarking tool, to allocate and initialize new stream connectors
from a stream endpoint descriptor (sedesc).
2026-07-01 15:22:14 +02:00
Frederic Lecaille
617bfe95f2 MINOR: stconn: add sc_hastream() and __sc_hastream() helpers
This patch introduces the sc_hastream() and __sc_hastream() inline
helpers to retrieve a haload stream context (struct hastream) from
a stream connector.

These functions allow the stconn layer to safely access haload-specific
stream data when the application type is OBJ_TYPE_HXLOAD.
2026-07-01 15:22:14 +02:00
Frederic Lecaille
d28f0cb219 MINOR: obj_type: add OBJ_TYPE_HALOAD for haload stream objects
This patch introduces the OBJ_TYPE_HXLOAD object type to distinguish
the haload stream objects (struct hastream).

It also adds the associated inline helper functions objt_hastream()
and __objt_hastream() to allow safe casting and retrieval of
hastream contexts from a generic object pointer, following the
standard container_of pattern.
2026-07-01 15:22:14 +02:00
Frederic Lecaille
0ecbf8ffb4 MINOR: hldstream: add definition of hldstream struct objects
haload is a client-side HTTP benchmarking tool designed to manage
concurrent HTTP streams.

This patch defines the hldstream C structure, which serves as the
core object to represent a haload HTTP stream for all the HTTP protocol.
It will be used by the upcoming haload module to handle specialized
stream contexts.
2026-07-01 15:22:14 +02:00
Frederic Lecaille
6e3ba99ca8 MINOR: trace: add definitions for haload streams
haload is the successor to the h1load HTTP benchmarking tool.

This patch adds haload stream definitions as arguments for the TRACE API.
These will be used by the upcoming haload module, which will handle
hldstream struct objects instead of regular stream structs.
2026-07-01 15:22:14 +02:00
Frederic Lecaille
477f29bf48 MINOR: init: add no listener mode
Introduce the new <no_listener_mode> global variable to define a new operating mode
for haproxy. This variable can be set to 1 to allow haproxy to start without
any listeners. Without such a setting, haproxy refuses to start without listener.

During the initialization cycle, setting this variable to 1 ensures that the
lack of configured listeners is no longer treated as a fatal error. This allows
programs based on haproxy source code to initialize the stack and use its
features even without a frontend. This will be the case for haload.
2026-07-01 15:22:14 +02:00
Frederic Lecaille
01f4e33ead MINOR: hbuf: new lightweight hbuf API
Add a new lightweight hbuf API to buffer formatted strings, similar to the
existing buffer API (struct buffer), extracting the code which already does this
in haterm_init.c. This is used by haterm to build its configuration in memory
(fileless mode). And this will be used by haload to do the same thing.

Update haterm to use this new API.

Note: hstream_str_buf_append() has been renamed to hbuf_str_append().
2026-07-01 15:22:14 +02:00
Olivier Houchard
f59da779ca BUG/MEDIUM: servers: Use a refcount for port_range and free it properly
port_range was never freed. That used not to be a problem, but now that
we can dynamically add and remove servers, it becomes one, as that leads
to a memory leak each time a server with a "source" directive is destroyed.
However, just adding a free() is not enough. We have to add a refcount,
because the server is not the only one with a reference to it. We may
also have one in fdinfo, so that we know which port to release when we
finally close the fd.
So add a refcount, and make sure to call port_range_release() when a
server is destroyed.

This should be backported up to 3.0.
2026-07-01 14:29:57 +02:00
Alexander Stephan
af9bb8507d DOC: server: document 'set server name' CLI command
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Document the new 'set server <b>/<s> name <newname>' CLI command in
management.txt.

The documentation states the two preconditions that gate the operation
(server must be in maintenance, server's name must not be statically
referenced via use-server / track / ARGT_SRV), notes that the command
is not gated by a per-backend opt-in directive (parity with 'add
server' / 'del server'), and mentions the EVENT_HDL_SUB_SERVER_NAME
event published on successful rename so Lua and other event consumers
know to subscribe to it.
2026-07-01 09:32:32 +02:00
Alexander Stephan
4949b0a9a7 REGTESTS: server: add test for 'set server name' CLI command
Tests cover:
  - error cases: missing name, not-in-maintenance, invalid chars
    (rejected by invalid_char()), duplicate name in the same backend,
    name-referenced server (use-server target, track target)
  - same-name rename as a no-op success
  - successful rename with verification via 'show servers state'
  - old name no longer resolves after rename
  - round-trip rename back to original name
  - traffic still works after rename round-trip

The use-server and tracked-server cases exercise the SRV_F_NAME_REFD
gating added in the preceding patch. Servers pinned only via resolvers
(SRV_F_NON_PURGEABLE without SRV_F_NAME_REFD) remain renamable; that
positive case is not exercised here as it would require a real DNS
resolver in the test environment.
2026-07-01 09:32:30 +02:00
Alexander Stephan
e4bc2d6a83 MEDIUM: server: add 'set server name' CLI command for runtime server renaming
Add the ability to rename a HAProxy server at runtime via the CLI:

  set server <backend>/<server> name <newname>

This is useful in slot-based dynamic scaling setups where servers are
pre-allocated with generic names (e.g. srv001, srv002) but the operator
wants the names to reflect the current workload (e.g. pod name or
IP:port) for observability and server-state-file consistency.

The implementation:
  - validates the new name: non-empty, passes invalid_char() check
    (allows [A-Za-z0-9_:.-]), and fits in the event data name field
  - requires the server to be administratively in maintenance mode
    (same precondition as 'del server')
  - rejects the rename if the server has SRV_F_NAME_REFD set (use-server
    target, track target, sample-fetch ARGT_SRV referent) - keeps the
    running state consistent with the configuration text
  - re-indexes the server in the name tree under thread_isolate(),
    mirroring the locking pattern used by 'add server' / 'del server'
  - publishes a new EVENT_HDL_SUB_SERVER_NAME event with the old and
    new names so downstream consumers (logs, observability backends)
    can track the rename
  - frees the old name immediately under thread isolation: srv_name
    sample consumers (ACLs, log formats, ...) act on the fetched pointer
    within the current task and do not retain it across wake-ups, so
    no extra deferred-free machinery is needed

There is no opt-in directive: like 'add server' and 'del server', the
operation is gated by the server's properties rather than by a
per-backend toggle. This avoids the runtime-surprise failure mode
where an operator discovers at the CLI that renaming is forbidden by
a missing 'option server-rename' rather than by an actual structural
reference.

This feature was discussed in:
  https://github.com/haproxy/haproxy/issues/952
2026-07-01 09:31:07 +02:00
Alexander Stephan
47b20a72e2 MINOR: server: distinguish name references with new SRV_F_NAME_REFD flag
Until now, every form of "this server is referenced by something in the
running config" was collapsed onto a single flag, SRV_F_NON_PURGEABLE,
which prevents the server from being removed via 'del server'. This
catches everything but conflates two distinct properties:

  - the server object itself is pinned by another runtime structure
    (e.g. DNS resolution attached to it), versus
  - the server's *name* is referenced statically (use-server rules,
    track chains, sample-fetch arguments of type ARGT_SRV)

These differ for any operation that touches the name but not the
object identity, e.g. the runtime rename feature added next. Removing
a name-referenced server is still forbidden (the rule text would
dangle), but renaming such a server should also be forbidden for the
same reason - while renaming a resolver-pinned server is fine, since
the resolver holds the object pointer and doesn't care about the name.

Introduce SRV_F_NAME_REFD for the name-reference case and move the
three name-based setters (sample.c ARGT_SRV resolution, proxy.c
use-server resolution, server.c track chain setup) from
SRV_F_NON_PURGEABLE to SRV_F_NAME_REFD. The resolvers.c call site
keeps SRV_F_NON_PURGEABLE since it is the object-pinned case.

Adjust 'del server' to check both flags so the set of servers it
refuses to remove is unchanged: same observable behavior, just a
richer internal taxonomy.

A subsequent patch introducing 'set server name' will gate on
SRV_F_NAME_REFD only.
2026-07-01 09:11:18 +02:00
Alexander Stephan
9475e69920 BUG/MINOR: sample: set SMP_F_CONST on srv_name fetch
smp_fetch_srv_name() stored a raw pointer to srv->id in the sample
without setting SMP_F_CONST. Every other sibling id-pointer fetch
(smp_fetch_be_name on px->id, smp_fetch_fe_name on fe->id, the SSL
helpers using OBJ_nid2sn() / SSL_get_cipher_name(), etc.) correctly
sets SMP_F_CONST to prevent in-place mutation by converters such as
,upper / ,lower / ,regsub.

Without SMP_F_CONST, an expression like srv_name,lower would write
into srv->id for the lifetime of the process. In practice this has
gone unnoticed because srv->id is a private allocation that is never
read back by name, but the bug is real and the divergence from the
other id fetches is unintentional.

This becomes more important with the introduction of runtime server
renaming (next patch in series): SMP_F_CONST ensures that callers go
through smp_make_rw() / smp_dup() before mutating, isolating the
sample's bytes from the server's id storage.

This is a stand-alone fix and should be backported.
2026-07-01 09:11:18 +02:00
Steven Honson
7aec1e6d9a BUG/MEDIUM: server: initialise agent.health in srv_settings_init()
srv_settings_init() sets agent.rise but forgets agent.health, while
srv_settings_cpy() sets both. check.health is fixed up later when the
server's admin state is updated at startup, but nothing does the same
for agent.health.

This used to be harmless because servers were always set up through
srv_settings_cpy(). But since 49a619aca ("MEDIUM: proxy: no longer
allocate the default-server entry by default") the defsrv pointer is
NULL when a proxy has no "default-server" line, and srv_settings_cpy()
then falls back to srv_settings_init(). So a server whose agent-check is
declared entirely on its "server" line ends up with agent.health == 0,
which is below agent.rise.

The wrong value only bites when the server has to come back up. While it
stays up nobody notices agent.health is 0, but as soon as the regular
health check fails and recovers, agent.health is still 0 (below rise) and
check_notify_success() won't bring the server back up. The agent never
sends an explicit "up", which is the only thing that raises agent.health,
so the server stays down for good. Moving the agent settings to a
"default-server" line works around it.

Just initialise agent.health in srv_settings_init() like
srv_settings_cpy() already does.

This should be backported to 3.3 and 3.4.
2026-06-30 17:39:07 +02:00
Amaury Denoyelle
ff6bb343f4 MINOR: hq-interop: trace HTX headers
Some checks failed
FreeBSD / clang (push) Waiting to run
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Similar to the previous patch, complete HTTP/0.9 user traces by logging
received HTX headers on request (BE side) or response (FE). This is only
for debugging purpose : the final HTTP/0.9 content does not contain any
of these.
2026-06-30 10:40:50 +02:00
Amaury Denoyelle
c4d5b78c57 MINOR: hq-interop: trace transcoding of response status line
Add a user trace when HTTP/0.9 response is either emitted (FE side) or
received (BE). The status code is displayed despite not being present in
the HTTP/0.9 response.
2026-06-30 10:39:35 +02:00
Amaury Denoyelle
151a75ef7b MINOR: hq-interop: add request start-line traces
Add traces to log the start-line of received (FE side) or sent (BE)
requests.

This uses a similar pattern with already supported HTTP/3 header traces.
However, this only requires minimal trace verbosity. This is because
these traces will be mostly useful for QUIC interop testing. However it
is probably not desirable to use advanced verbosity in this context to
avoid increasing the traces output.
2026-06-30 10:39:29 +02:00
Amaury Denoyelle
8288e68f44 MINOR: mux_quic: add minimal traces for QUIC MUX init/release
Add user traces in qcm_init() and qcc_release(). This is useful to be
able to quickly account connection allocation/release without using the
developer trace level.
2026-06-29 16:35:16 +02:00
Amaury Denoyelle
ea54d3fd1b BUG/MINOR: h3: adjust HTTP headers traces
In a recent patch, dedicated HTTP/3 traces have been added to log the
transfered HTTP content, with the start/status line and headers as well.

This patch adjusts these traces, correcting "qcc" typo to "qcs". It also
now correctly pass qcc and qcs as argument, which is used for trace
follow.

No need to backport unless HTTP/3 header traces are picked to previous
releases.
2026-06-29 16:33:55 +02:00
Amaury Denoyelle
b68be6d0a2 BUG/MINOR: hq-interop: support transcoding of absolute URI
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
On the backend side, HTTP/0.9 transcoder is responsible to convert a HTX
request into a HTTP start line. In particular, path is generated from
the HTX request URI.

However, an absolute URI was not converted correctly in a HTTP/0.9
simple path. This occurs notably in most cases when using HTTP/2 or 3 on
the frontend side.

This issue was detected when running QUIC interop. Some servers
implementation such as picoquic would reject these requests as they are
considered invalid.

To adjust this, extract the path component from HTX uri using
http_uri_parser API.

This should be backported up to 3.3 as this is a QUIC backend fix.
2026-06-29 16:04:57 +02:00
Amaury Denoyelle
8bc48dfc1e BUG/MINOR: hq-interop: fix transcoding of wrapping response buffer
The below patch has implemented support for response wrapping in HTTP/0.9
transcoder, similar to what is already performed in HTTP/3.

  1e144c488c
  BUG/MINOR: hq-interop: support response buffer wrapping

However, some bits were incorrectly written and the transcoding would
not be able to handle all of the wrapping data in one pass, despite no
crash possible. This patch fixes these, so that a wrapping response can
be handled in a single pass by the HTTP/0.9 transcoder if there is
enough room in the output HTX buffer.

This should fix github issue #3430.

This should be backported up to 3.3.
2026-06-29 16:04:57 +02:00
William Lallemand
9d64d390c8 MEDIUM: httpclient: initialize the httpclient with default SSL values
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
FreeBSD / clang (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
The current httpclient implementation does not initialize its server
with the options from the global section: ciphers, ciphersuites and
various SSL options are always the default of the SSL library.

This patch changes the behavior and apply the ssl-default-server-*
keywords to the httpclient SSL server.
2026-06-26 17:25:50 +02:00
William Lallemand
ce417b2fb9 MINOR: ssl: export ssl_sock_init_srv()
Export ssl_sock_init_srv() so it can be called at other places where we
initialize servers
2026-06-26 17:25:50 +02:00
Frederic Lecaille
1cb254ad77 BUG/MEDIUM: mux_quic: fix memory leak of rx app_buf on stream free
When freeing a QUIC stream (qcs), the receive application buffer
(qcs->rx.app_buf) was not released if it still contained data or
had been allocated. This led to a memory leak over time as streams
were opened and closed.

Fix this by explicitly freeing qcs->rx.app_buf via b_free() in
qcs_free() if its size is non-zero, and call offer_buffers() to
notify the buffer pool.

This should be backported as far as 2.6.
2026-06-26 17:16:38 +02:00
Willy Tarreau
78639c7cf7 CI: github: remove OpenTracing leftovers
When removing support for USE_OT=1 I only saw the tests but not the
build matrix, and this now causes build failures on the CI for tests
with "all features". Let's remove it there as well as the checks for
the OT cache and libs.
2026-06-26 16:45:38 +02:00
Amaury Denoyelle
09172169bc BUG/MEDIUM: h3: fix trace crash on frontend response headers
Some checks failed
FreeBSD / clang (push) Waiting to run
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Fix segfault when using HTTP/3 header traces on the frontend side. This
occured because headers list was dumped prior to the insertion of the
end marker.

This issue is introduced by the following patch :
  commit 00c081b5f3
  MINOR: h3: trace HTTP headers on FE side

No need to backport, unless HTTP/3 header traces are picked to previous
releases.
2026-06-26 11:32:10 +02:00
Willy Tarreau
66fe27298f MAJOR: ot: remove deprecated OpenTracing support
OpenTracing support has long been best-effort and was deprecated in 3.3
with removal planned in 3.5. Let's clean it up now.

This commit removes addons/ot, the build script, ARGC_OT, USE_OT and
OT_* variables in the Makefile, and replaces the config section with a
mention for the OpenTelemetry filter instead.

For more info, see GH issues #1640 and #2782, as well as the wiki's
"breaking changes" page.
2026-06-26 11:27:07 +02:00
Willy Tarreau
d071a736c4 CLEANUP: trace: remove backend retrieval attempt from conn->target
Since we may no longer see conn->target point to the proxy, let's drop
the retrieval attempt for a backend there in __trace_enabled().
2026-06-26 11:10:43 +02:00
Willy Tarreau
7adf94cfe1 CLEANUP: backend: drop checks for OBJ_TYPE_PROXY in connect() code
In tcp_connect_server(), uxst_connect_server(), and quic_connect_server(),
we can no longer see obj_type(conn->target) == OBJ_TYPE_PROXY so let's
drop that code. This implies that srv may no longer be NULL so we can
drop these checks as well.
2026-06-26 11:09:34 +02:00
Willy Tarreau
6864526d05 CLEANUP: connection: remove some checks for objt_proxy(conn->target)
Since a connection's target may no longer be a proxy and is necessarily
a server, let's simplify such checks. This is essentially in mux install
code and in the debugging code.
2026-06-26 11:09:34 +02:00
Willy Tarreau
848668c7ac MEDIUM: cli/show-fd: no longer accept filtering for dispatch mode
"show fd" supports various flags, one of which is 'd' for "dispatch",
which also catches "transparent", in fact, connections whose target is
a proxy. Since these can no longer happen, let's remove that. The 'b'
and 's' flags are now aliases of each other for simplicity.
2026-06-26 10:53:51 +02:00
Willy Tarreau
be8159248f MAJOR: proxy: remove support for "dispatch" and "transparent" proxy keywords
These ones were deprecated in 3.3-dev2 with commits 5c15ba5eff ("MEDIUM:
proxy: mark the "dispatch" directive as deprecated") and e93f3ea3f8
("MEDIUM: proxy: deprecate the "transparent" and "option transparent"
directives"), and were planned for removal in 3.5. See also:

   https://github.com/orgs/haproxy/discussions/2921

as well as the wiki page about breaking changes.

They've lived their lives and always cause internal limitations
(exceptions between connecting to server or connecting to proxy), and
are even confusing to some extents (especially "transparent" which users
often get wrong).

This commit removes the ability to configure them, tests based on them
and all the doc related to them. The keywords remain detected by the
parser and indicate how to proceed instead.

It's likely that other deeper parts will be changed as well (e.g.
conn->target will no longer be of OBJ_TYPE_PROXY). This will be done
over the long term.
2026-06-26 10:51:16 +02:00
Willy Tarreau
b12ef0aa44 MINOR: proxy: permit to report version info for option deprecation
It's already possible to report that some options are not supported due
to build options by passing 0 instead of PR_CAP_* in the option's cap
field. Let's extend that by passing a non-zero value in the val field,
where the 3rd byte will be the major version and the 4th one the minor.
In this case haproxy will now indicate that support for that option was
removed in that version.
2026-06-26 10:23:23 +02:00
259 changed files with 8756 additions and 14456 deletions

2
.github/matrix.py vendored
View file

@ -172,7 +172,6 @@ def main(ref_name):
"FLAGS": [
'DEBUG="-DDEBUG_LIST"',
"USE_ZLIB=1",
"USE_OT=1",
"OT_INC=${HOME}/opt-ot/include",
"OT_LIB=${HOME}/opt-ot/lib",
"OT_RUNPATH=1",
@ -206,7 +205,6 @@ def main(ref_name):
'ARCH_FLAGS="-g -fsanitize=address"',
'OPT_CFLAGS="-O1"',
"USE_ZLIB=1",
"USE_OT=1",
"DEBUG=-DDEBUG_STRICT=2",
"OT_INC=${HOME}/opt-ot/include",
"OT_LIB=${HOME}/opt-ot/lib",

View file

@ -21,7 +21,7 @@ jobs:
- name: "Build and test on FreeBSD"
uses: vmactions/freebsd-vm@v1
with:
release: "14.3"
release: "14.4"
prepare: |
pkg update -f && pkg upgrade -y && pkg install -y openssl git gmake lua54 socat pcre2 python3
run: |

View file

@ -65,13 +65,6 @@ jobs:
path: '~/opt/'
key: ssl-${{ steps.generate-cache-key.outputs.key }}
- name: Cache OpenTracing
if: ${{ contains(matrix.FLAGS, 'USE_OT=1') }}
id: cache_ot
uses: actions/cache@v5
with:
path: '~/opt-ot/'
key: ${{ matrix.os }}-ot-${{ matrix.CC }}-${{ env.OT_CPP_VERSION }}-${{ contains(matrix.name, 'ASAN') }}
- name: Add i386 architecture
if: ${{ matrix.CC == 'i686-linux-gnu-gcc' }}
run: sudo dpkg --add-architecture i386
@ -99,10 +92,6 @@ jobs:
- name: Install SSL ${{ matrix.ssl }}
if: ${{ matrix.ssl && matrix.ssl != 'stock' && steps.cache_ssl.outputs.cache-hit != 'true' }}
run: env ${{ matrix.ssl }} scripts/build-ssl.sh
- name: Install OpenTracing libs
if: ${{ contains(matrix.FLAGS, 'USE_OT=1') && steps.cache_ot.outputs.cache-hit != 'true' }}
run: |
OT_PREFIX=${HOME}/opt-ot scripts/build-ot.sh
- name: Build WURFL
if: ${{ contains(matrix.FLAGS, 'USE_WURFL=1') }}
run: make -C addons/wurfl/dummy

116
CHANGELOG
View file

@ -1,6 +1,122 @@
ChangeLog :
===========
2026/07/08 : 3.5-dev2
- MINOR: proxy: permit to report version info for option deprecation
- MAJOR: proxy: remove support for "dispatch" and "transparent" proxy keywords
- MEDIUM: cli/show-fd: no longer accept filtering for dispatch mode
- CLEANUP: connection: remove some checks for objt_proxy(conn->target)
- CLEANUP: backend: drop checks for OBJ_TYPE_PROXY in connect() code
- CLEANUP: trace: remove backend retrieval attempt from conn->target
- MAJOR: ot: remove deprecated OpenTracing support
- BUG/MEDIUM: h3: fix trace crash on frontend response headers
- CI: github: remove OpenTracing leftovers
- BUG/MEDIUM: mux_quic: fix memory leak of rx app_buf on stream free
- MINOR: ssl: export ssl_sock_init_srv()
- MEDIUM: httpclient: initialize the httpclient with default SSL values
- BUG/MINOR: hq-interop: fix transcoding of wrapping response buffer
- BUG/MINOR: hq-interop: support transcoding of absolute URI
- BUG/MINOR: h3: adjust HTTP headers traces
- MINOR: mux_quic: add minimal traces for QUIC MUX init/release
- MINOR: hq-interop: add request start-line traces
- MINOR: hq-interop: trace transcoding of response status line
- MINOR: hq-interop: trace HTX headers
- BUG/MEDIUM: server: initialise agent.health in srv_settings_init()
- BUG/MINOR: sample: set SMP_F_CONST on srv_name fetch
- MINOR: server: distinguish name references with new SRV_F_NAME_REFD flag
- MEDIUM: server: add 'set server name' CLI command for runtime server renaming
- REGTESTS: server: add test for 'set server name' CLI command
- DOC: server: document 'set server name' CLI command
- BUG/MEDIUM: servers: Use a refcount for port_range and free it properly
- MINOR: hbuf: new lightweight hbuf API
- MINOR: init: add no listener mode
- MINOR: trace: add definitions for haload streams
- MINOR: hldstream: add definition of hldstream struct objects
- MINOR: obj_type: add OBJ_TYPE_HALOAD for haload stream objects
- MINOR: stconn: add sc_hastream() and __sc_hastream() helpers
- MINOR: stconn: export sc_new()
- MINOR: server: export functions used during server initialization
- MINOR: haload: import source code and documentation
- MINOR: log: add app_log_raw() and send_log_raw() for binary-safe logging
- BUG/MINOR: init: fix default global settings being overwritten by -G
- BUG/MINOR: tools: fix invalid character detection in strl2ic()
- BUG/MAJOR: htx: Don't swap buffers for empty HTX message with an error
- CLEANUP: applet/http-client: Don't needlessly copy HTX flags after htx_xfer()
- BUG/MINOR: mux-quic: Fix handling EOM after in qcs_http_rcv_buf()
- BUG/MINOR: http-htx: Don't by-pass HTX API when merging cookie values
- BUG/MEDIUM: h3: fix parser desync on error with multiple frames
- BUG/MINOR: mux_quic: prevent multiple STOP_SENDING emission per stream
- BUILD: quic: workaround a gcc bug saying "maybe used uninitialized" when USE_TRACE=0
- CLEANUP: traces: get rid of a few rare empty args in TRACE calls
- MINOR: compiler: add a macro to ignore all arguments
- MINOR: trace: always pretend to use args when disabled
- BUILD: ssl: avoid a wrong null deref warning in ssl_sock_handshake
- CLEANUP: haproxy: remove -dt parsing and help when !USE_TRACE
- CLEANUP: mux-h2/traces: remove unused trace code when building without USE_TRACE
- CLEANUP: debug/trace: remote "debug dev trace" when USE_TRACE is not set
- BUG/MINOR: trace/quic_frame: use buf, not trace_buf in chunk_frm_appendf()
- CLEANUP: trace/h3: allow to disable traces in H3
- CLEANUP: trace/config: do not register section "traces" with USE_TRACE=0
- CLEANUP: trace/tree-wide: drop trace decoding/definition when USE_TRACE=0
- BUILD: makefile: only build trace.c and ssl_trace.c when USE_TRACE is set
- BUILD: makefile: add macros enable_opts and disable_opts
- BUILD: makefile: add an option to enable or disable HTTP/2 (USE_H2)
- BUILD: makefile: add an option to enable or disable FCGI (USE_FCGI)
- BUILD: makefile: add an option to enable or disable SPOE (USE_SPOE)
- BUILD: makefile: add a new generic target "tiny"
- BUG/MEDIUM: mux_quic: do not free QCS if STOP_SENDING to sent
- MINOR: mux_quic: use separate error code for STOP_SENDING
- MINOR: mux_quic: adjust shut stream callback
- BUG/MEDIUM: mux_quic: complete stream shutdown for read channel
- BUG/MINOR: quic: ignore STREAM after MUX closure on BE side
- BUG/MEDIUM: fd: Fix a deadlock when closing other tgroups fds
- MEDIUM: quic: remove deprecated keywords
- DEV: patchbot: keep the review start in sync with the radios on reload
- DEV: patchbot: only display the first 8 chars of the commit id
- DEV: patchbot: pass the branch version to the generated page
- DEV: patchbot: let the page fetch the shared review state
- DEV: patchbot: let the page save the review edits to the server
- DEV: patchbot: let the page edit and delete whole notes
- DEV: patchbot: gray the save button when there is nothing to save
- DEV: patchbot: don't pretend a save succeeded when the server ignored it
- DEV: patchbot: tolerate polluted save responses and show server warnings
- DEV: patchbot: repeat the syncing buttons at the bottom of the page
- DEV: patchbot: update: add an awk backend to persist review edits
- DEV: patchbot: update: return the stored overlay as JSON on GET
- DEV: patchbot: update: support replacing a whole note blob (setnotes)
- DEV: patchbot: update: report git commit failures in the save response
- DEV: patchbot: update: report the exact git error to the user
- DEV: patchbot: update: never write to stderr, thttpd sends it first
- DEV: patchbot: update: name the touched commits in the storage messages
- DEV: patchbot: document the shared review persistence
- BUG/MINOR: haload: fix spurious task wakeup in hld_strm_task()
- BUG/MINOR: hbuf: treat unexpected escape sequences as literals
- BUG/MEDIUM: tcpcheck: Add proxy used for healthcheck sections in proxies list
- BUG/MINOR: sample: Fix a possible underflow on be2hex for large chunk size
- MINOR: chunks: Add function to get a large/regular chunk depending on a buffer
- BUG/MEDIUM: chunk: Review chunks usage to not retrieve a large buffer by error
- MINOR: htx: Add a field to save the headers data size
- MEDIUM: htx: Be sure size of headers never exceed regular buffer on update
- BUG/MINOR: stream: Fix custom timeouts initialization when setting backend
- REGTESTS: Improve script testing the set-timeout action
- BUG/MINOR: stream: Fix custom max-retries initialization when setting backend
- MINOR: stream: Add be_max_retries/cur_max_retries sample fetch functions
- BUG/MINOR: http-conv: Make url-dec failed if no space for trailing null byte
- MAJOR: mworker: remove deprecated "master-worker" global keyword
- DOC: haterm: add a missing 'haterm' build target on an example
- DOC: readme: add a pointer to haterm/haload docs
- MINOR: ocsp: Do not see ocsp loading failures as fatal anymore
- REGTESTS: Remove unused `add_range_to_test_list` function from `scripts/run-regtests.sh`
- REGTESTS: Remove unused `_version` function from `scripts/run-regtests.sh`
- REGTESTS: Migrate REQUIRE_OPTION to `haproxy -cc`
- REGTESTS: Remove support for `REQUIRE_OPTION` from scripts/run-regtests.sh
- REGTESTS: Migrate `REQUIRE_SERVICE=prometheus-exporter` to a `feature(PROMEX)` check
- REGTESTS: Remove support for `REQUIRE_SERVICE` from scripts/run-regtests.sh
- CI: github: update vmactions/freebsd-vm to 14.4
- MINOR: haload: move statistics header printing to mtask_cb
- MINOR: haterm: add note about QUIC usage on SSL port
- MINOR: haload: allow "0" shortcut for IPv4 bind address
2026/06/25 : 3.5-dev1
- BUG/MEDIUM: check: Skip tcpcheck post-config for external checks
- BUG/MEDIUM: check: Ignore small-buffer option when starting an external check

15
INSTALL
View file

@ -475,9 +475,9 @@ are the extra libraries that may be referenced at build time :
-------------------
Some build errors may happen depending on the options combinations or the
selected target. When facing build errors, if you know that your system is a
bit special or particularly old, start from TARGET=generic, it is easier to
start from there and fix the remaining issues than trying to degrade another
target. Common issues may include:
bit special or particularly old, start from TARGET=generic or TARGET=tiny; it
is easier to start from there and fix the remaining issues than trying to
degrade another target. Common issues may include:
- clock_gettime() not found
=> your system needs USE_RT=1
@ -579,6 +579,7 @@ and assign it to the TARGET variable :
- haiku for Haiku
- generic for any other OS or version.
- custom to manually adjust every setting
- tiny to turn all default options off by default
Example:
$ make -j $(nproc) TARGET=linux-glibc
@ -603,8 +604,14 @@ well use that property to define your own set of options. USE_POLL and USE_SLZ
can even be disabled by setting them to an empty string or a zero. For
example :
$ gmake TARGET=tiny USE_POLL="" USE_SLZ=0 TARGET_CFLAGS=-fomit-frame-pointer
$ gmake TARGET=small USE_POLL="" USE_SLZ=0 TARGET_CFLAGS=-fomit-frame-pointer
Note that when seeking the absolute smallest build (e.g. for low-spec embedded
devices), the "tiny" target disables everything configurable and generally
results in a binary that's 4-5 times smaller than the default one. From this
point it then becomes possible to enable only what is desired:
$ make -j $(nproc) TARGET=tiny USE_EPOLL=1 USE_DL=1 USE_OPENSSL=1
5.2) Adding extra CFLAGS for compiling
--------------------------------------

View file

@ -60,12 +60,14 @@
# USE_WURFL : enable WURFL detection library from Scientiamobile
# USE_OBSOLETE_LINKER : use when the linker fails to emit __start_init/__stop_init
# USE_THREAD_DUMP : use the more advanced thread state dump system. Automatic.
# USE_OT : enable the OpenTracing filter
# USE_MEMORY_PROFILING : enable the memory profiler. Linux-glibc only.
# USE_LIBATOMIC : force to link with/without libatomic. Automatic.
# USE_PTHREAD_EMULATION : replace pthread's rwlocks with ours
# USE_SHM_OPEN : use shm_open() for features that can make use of shared memory
# USE_KTLS : use kTLS.(requires at least Linux 4.17).
# USE_FCGI : enable the FCGI subsystem. Always on.
# USE_H2 : enable HTTP/2 subsystem. Always on.
# USE_SPOE : enable the SPOE subsystem. Always on.
#
# Options can be forced by specifying "USE_xxx=1" or can be disabled by using
# "USE_xxx=" (empty string). The list of enabled and disabled options for a
@ -125,11 +127,6 @@
# LUA_INC : force the include path to lua
# LUA_LIB_NAME : force the lib name (or automatically evaluated, by order of
# priority: lua5.5, lua55, lua5.4, lua54, lua5.3, lua53, lua).
# OT_DEBUG : compile the OpenTracing filter in debug mode
# OT_INC : force the include path to libopentracing-c-wrapper
# OT_LIB : force the lib path to libopentracing-c-wrapper
# OT_RUNPATH : add RUNPATH for libopentracing-c-wrapper to haproxy executable
# OT_USE_VARS : allows the use of variables for the OpenTracing context
# IGNOREGIT : ignore GIT commit versions if set.
# VERSION : force haproxy version reporting.
# SUBVERS : add a sub-version (eg: platform, model, ...).
@ -160,7 +157,7 @@ DOCDIR = $(PREFIX)/doc/haproxy
# following list (use the default "generic" if uncertain) :
# linux-glibc, linux-glibc-legacy, linux-musl, solaris, freebsd, freebsd-glibc,
# dragonfly, openbsd, netbsd, cygwin, haiku, aix51, aix52, aix72-gcc, osx, generic,
# custom
# custom, tiny
TARGET =
#### No longer used
@ -344,12 +341,12 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER USE_POLL \
USE_TPROXY USE_LINUX_TPROXY USE_LINUX_CAP \
USE_LINUX_SPLICE USE_LIBCRYPT USE_CRYPT_H USE_ENGINE \
USE_GETADDRINFO USE_OPENSSL USE_OPENSSL_WOLFSSL USE_OPENSSL_AWSLC \
USE_ECH USE_TRACE \
USE_ECH USE_TRACE USE_FCGI USE_H2 USE_SPOE \
USE_SSL USE_LUA USE_ACCEPT4 USE_CLOSEFROM USE_ZLIB USE_SLZ \
USE_CPU_AFFINITY USE_TFO USE_NS USE_DL USE_RT USE_LIBATOMIC \
USE_MATH USE_DEVICEATLAS USE_51DEGREES \
USE_WURFL USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL \
USE_THREAD_DUMP USE_EVPORTS USE_OT USE_QUIC USE_PROMEX \
USE_THREAD_DUMP USE_EVPORTS USE_QUIC USE_PROMEX \
USE_MEMORY_PROFILING USE_SHM_OPEN \
USE_STATIC_PCRE USE_STATIC_PCRE2 \
USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT \
@ -370,6 +367,15 @@ USE_POLL = default
# traces are always enabled
USE_TRACE = default
# FCGI is always enabled
USE_FCGI = default
# HTTP/2 is always enabled
USE_H2 = default
# SPOE is always enabled
USE_SPOE = default
# SLZ is always supported unless explicitly disabled by passing USE_SLZ=""
# or disabled by enabling ZLIB using USE_ZLIB=1
ifeq ($(USE_ZLIB:0=),)
@ -381,6 +387,13 @@ ifeq ($(TARGET),generic)
set_target_defaults = $(call default_opts,USE_POLL USE_TPROXY)
endif
# For embedded systems or to be used as a base, tiniest binary with fewest
# features. Only poll() is enabled to avoid issues with select().
ifeq ($(TARGET),tiny)
set_target_defaults = $(call disable_opts,$(use_opts)) $(call enable_opts,USE_POLL)
INSTALL = install -v
endif
# Haiku
ifeq ($(TARGET),haiku)
TARGET_LDFLAGS = -lnetwork
@ -573,6 +586,18 @@ ifneq ($(USE_ZLIB:0=),)
ZLIB_LDFLAGS = $(if $(ZLIB_LIB),-L$(ZLIB_LIB)) -lz
endif
ifneq ($(USE_H2:0=),)
OPTIONS_OBJS += src/mux_h2.o src/h2.o
endif
ifneq ($(USE_FCGI:0=),)
OPTIONS_OBJS += src/mux_fcgi.o src/fcgi-app.o src/fcgi.o
endif
ifneq ($(USE_SPOE:0=),)
OPTIONS_OBJS += src/mux_spop.o src/flt_spoe.o
endif
ifneq ($(USE_SLZ:0=),)
OPTIONS_OBJS += src/slz.o
endif
@ -635,6 +660,7 @@ endif
ifneq ($(USE_OPENSSL_AWSLC:0=),)
# always automatically set USE_OPENSSL
USE_OPENSSL := $(if $(USE_OPENSSL:0=),$(USE_OPENSSL:0=),implicit)
OPTIONS_OBJS += src/fips.o
endif
# This is for any variant of the OpenSSL API. By default it uses OpenSSL.
@ -648,7 +674,10 @@ ifneq ($(USE_OPENSSL:0=),)
OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o \
src/ssl_sample.o src/cfgparse-ssl.o src/ssl_gencert.o \
src/ssl_utils.o src/jwt.o src/ssl_clienthello.o src/jws.o src/acme.o \
src/acme_resolvers.o src/ssl_trace.o src/jwe.o
src/acme_resolvers.o src/jwe.o
ifneq ($(USE_TRACE:0=),)
OPTIONS_OBJS += src/ssl_trace.o
endif
endif
ifneq ($(USE_ENGINE:0=),)
@ -809,11 +838,6 @@ ifneq ($(USE_LINUX_CAP:0=),)
OPTIONS_OBJS += src/linuxcap.o
endif
ifneq ($(USE_OT:0=),)
$(call warning, The opentracing filter was deprecated in haproxy 3.3 and will be removed in 3.5.)
include addons/ot/Makefile
endif
ifneq ($(EXTRA_MAKE),)
include $(addsuffix /Makefile.mk,$(EXTRA_MAKE))
endif
@ -913,30 +937,31 @@ endif # TARGET
OBJS =
HATERM_OBJS =
HALOAD_OBJS =
ifneq ($(EXTRA_OBJS),)
OBJS += $(EXTRA_OBJS)
endif
OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.o \
OBJS += src/mux_h1.o src/log.o \
src/server.o src/stream.o src/tcpcheck.o src/http_ana.o \
src/stick_table.o src/tools.o src/mux_spop.o src/sample.o \
src/stick_table.o src/tools.o src/sample.o \
src/activity.o src/cfgparse.o src/peers.o src/cli.o \
src/backend.o src/connection.o src/resolvers.o src/proxy.o \
src/cache.o src/stconn.o src/http_htx.o src/debug.o \
src/check.o src/stats-html.o src/haproxy.o src/listener.o \
src/applet.o src/pattern.o src/cfgparse-listen.o \
src/flt_spoe.o src/cebis_tree.o src/http_ext.o \
src/cebis_tree.o src/http_ext.o \
src/http_act.o src/http_fetch.o src/cebs_tree.o \
src/cebib_tree.o src/http_client.o src/dns.o \
src/cebb_tree.o src/vars.o src/event_hdl.o src/tcp_rules.o \
src/trace.o src/stats-proxy.o src/pool.o src/stats.o \
src/stats-proxy.o src/pool.o src/stats.o \
src/cfgparse-global.o src/filters.o src/mux_pt.o \
src/flt_http_comp.o src/sock.o src/h1.o src/sink.o \
src/ceba_tree.o src/session.o src/payload.o src/htx.o \
src/cebl_tree.o src/ceb32_tree.o src/ceb64_tree.o \
src/server_state.o src/proto_rhttp.o src/flt_trace.o src/fd.o \
src/task.o src/map.o src/fcgi-app.o src/h2.o src/mworker.o \
src/task.o src/map.o src/mworker.o \
src/tcp_sample.o src/mjson.o src/h1_htx.o src/tcp_act.o \
src/ring.o src/flt_bwlim.o src/acl.o src/thread.o src/queue.o \
src/http_rules.o src/http.o src/channel.o src/proto_tcp.o \
@ -966,7 +991,13 @@ ifneq ($(TRACE),)
OBJS += src/calltrace.o
endif
HATERM_OBJS += $(OBJS) src/haterm_init.o
ifneq ($(USE_TRACE:0=),)
OBJS += src/trace.o
endif
HATERM_OBJS += $(OBJS) src/haterm_init.o src/hbuf.o
HALOAD_OBJS += $(OBJS) src/haload_init.o src/haload.o src/hbuf.o
# Used only for forced dependency checking. May be cleared during development.
INCLUDES = $(wildcard include/*/*.h)
@ -1018,6 +1049,9 @@ haproxy: $(OPTIONS_OBJS) $(OBJS)
haterm: $(OPTIONS_OBJS) $(HATERM_OBJS)
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
haload: $(OPTIONS_OBJS) $(HALOAD_OBJS)
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
objsize: haproxy
$(Q)objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort

View file

@ -41,6 +41,7 @@ Please refer to the following files depending on what you're looking for:
- [BRANCHES](BRANCHES) to understand the project's life cycle and what version to use
- [LICENSE](LICENSE) for the project's license
- [CONTRIBUTING](CONTRIBUTING) for the process to follow to submit contributions
- [ doc/haterm.txt ](doc/haterm.txt) and [ doc/haload.txt ](doc/haload.txt) for load generation utilities
The more detailed documentation is located into the doc/ directory:

View file

@ -1,2 +1,2 @@
$Format:%ci$
2026/06/25
2026/07/08

View file

@ -1 +1 @@
3.5-dev1
3.5-dev2

View file

@ -1 +0,0 @@
Miroslav Zagorac <mzagorac@haproxy.com>

View file

@ -1 +0,0 @@
Miroslav Zagorac <mzagorac@haproxy.com>

View file

@ -1,73 +0,0 @@
# USE_OT : enable the OpenTracing filter
# OT_DEBUG : compile the OpenTracing filter in debug mode
# OT_INC : force the include path to libopentracing-c-wrapper
# OT_LIB : force the lib path to libopentracing-c-wrapper
# OT_RUNPATH : add libopentracing-c-wrapper RUNPATH to haproxy executable
# OT_USE_VARS : allows the use of variables for the OpenTracing context
OT_DEFINE =
OT_CFLAGS =
OT_LDFLAGS =
OT_DEBUG_EXT =
OT_PKGSTAT =
OTC_WRAPPER = opentracing-c-wrapper
ifneq ($(OT_DEBUG),)
OT_DEBUG_EXT = _dbg
OT_DEFINE = -DDEBUG_OT
endif
ifeq ($(OT_INC),)
OT_PKGSTAT = $(shell pkg-config --exists $(OTC_WRAPPER)$(OT_DEBUG_EXT); echo $$?)
OT_CFLAGS = $(shell pkg-config --silence-errors --cflags $(OTC_WRAPPER)$(OT_DEBUG_EXT))
else
ifneq ($(wildcard $(OT_INC)/$(OTC_WRAPPER)/.*),)
OT_CFLAGS = -I$(OT_INC) $(if $(OT_DEBUG),-DOTC_DBG_MEM)
endif
endif
ifeq ($(OT_PKGSTAT),)
ifeq ($(OT_CFLAGS),)
$(error OpenTracing C wrapper : can't find headers)
endif
else
ifneq ($(OT_PKGSTAT),0)
$(error OpenTracing C wrapper : can't find package)
endif
endif
ifeq ($(OT_LIB),)
OT_LDFLAGS = $(shell pkg-config --silence-errors --libs $(OTC_WRAPPER)$(OT_DEBUG_EXT))
else
ifneq ($(wildcard $(OT_LIB)/lib$(OTC_WRAPPER).*),)
OT_LDFLAGS = -L$(OT_LIB) -l$(OTC_WRAPPER)$(OT_DEBUG_EXT)
ifneq ($(OT_RUNPATH),)
OT_LDFLAGS += -Wl,--rpath,$(OT_LIB)
endif
endif
endif
ifeq ($(OT_LDFLAGS),)
$(error OpenTracing C wrapper : can't find library)
endif
OPTIONS_OBJS += \
addons/ot/src/cli.o \
addons/ot/src/conf.o \
addons/ot/src/event.o \
addons/ot/src/filter.o \
addons/ot/src/group.o \
addons/ot/src/http.o \
addons/ot/src/opentracing.o \
addons/ot/src/parser.o \
addons/ot/src/pool.o \
addons/ot/src/scope.o \
addons/ot/src/util.o
ifneq ($(OT_USE_VARS),)
OT_DEFINE += -DUSE_OT_VARS
OPTIONS_OBJS += \
addons/ot/src/vars.o
endif
OT_CFLAGS := $(OT_CFLAGS) $(OT_DEFINE)

View file

@ -1,799 +0,0 @@
-----------------------------------------
The HAProxy OpenTracing filter (OT)
Version 1.0
( Last update: 2020-12-10 )
-----------------------------------------
Author : Miroslav Zagorac
Contact : mzagorac at haproxy dot com
SUMMARY
--------
0. Terms
1. Introduction
2. Build instructions
3. Basic concepts in OpenTracing
4. OT configuration
4.1. OT scope
4.2. "ot-tracer" section
4.3. "ot-scope" section
4.4. "ot-group" section
5. Examples
5.1 Benchmarking results
6. OT CLI
7. Known bugs and limitations
0. Terms
---------
* OT: The HAProxy OpenTracing filter
OT is the HAProxy filter that allows you to send data to distributed
tracing systems via the OpenTracing API.
1. Introduction
----------------
Nowadays there is a growing need to divide a process into microservices and
there is a problem of monitoring the work of the same process. One way to
solve this problem is to use distributed tracing service in a central location,
the so-called tracer.
OT is a feature introduced in HAProxy 2.4. This filter enables communication
via the OpenTracing API with OpenTracing compatible servers (tracers).
Currently, tracers that support this API include Datadog, Jaeger, LightStep
and Zipkin.
Note: The OpenTracing filter shouldn't be used for new designs as OpenTracing
itself is no longer maintained nor supported by its authors. As such
OpenTracing will be deprecated in 3.3 and removed in 3.5. A replacement
filter based on OpenTelemetry is available since 3.4 with complete build
instructions currently at:
https://github.com/haproxytech/haproxy-opentelemetry/
The OT filter is a standard HAProxy filter, so what applies to others also
applies to this one (of course, by that I mean what is described in the
documentation, more precisely in the doc/internals/filters.txt file).
The OT filter activation is done explicitly by specifying it in the HAProxy
configuration. If this is not done, the OT filter in no way participates
in the work of HAProxy.
As for the impact on HAProxy speed, this is documented with several tests
located in the test directory, and the result is found in the README-speed-*
files. In short, the speed of operation depends on the way it is used and
the complexity of the configuration, from an almost immeasurable impact to
a significant deceleration (5x and more). I think that in some normal use
the speed of HAProxy with the filter on will be quite satisfactory with a
slowdown of less than 4% (provided that no more than 10% of requests are
sent to the tracer, which is determined by the keyword 'rate-limit').
The OT filter allows intensive use of ACLs, which can be defined anywhere in
the configuration. Thus, it is possible to use the filter only for those
connections that are of interest to us.
2. Build instructions
----------------------
OT is the HAProxy filter and as such is compiled together with HAProxy.
To communicate with some OpenTracing compatible tracer, the OT filter uses the
OpenTracing C Wrapper library (which again uses the OpenTracing CPP library).
This means that we must have both libraries installed on the system on which
we want to compile or use HAProxy.
Instructions for compiling and installing both required libraries can be
found at https://github.com/haproxytech/opentracing-c-wrapper .
Also, to use the OT filter when running HAProxy we need to have an OpenTracing
plugin for the tracer we want to use. We will return to this later, in
section 5.
The OT filter can be more easily compiled using the pkg-config tool, if we
have the OpenTracing C Wrapper library installed so that it contains pkg-config
files (which have the .pc extension). If the pkg-config tool cannot be used,
then the path to the directory where the include files and libraries are
located can be explicitly specified.
Below are examples of the two ways to compile HAProxy with the OT filter, the
first using the pkg-congfig tool and the second explicitly specifying the path
to the OpenTracing C Wrapper include and library.
Note: prompt '%' indicates that the command is executed under a unprivileged
user, while prompt '#' indicates that the command is executed under the
root user.
Example of compiling HAProxy using the pkg-congfig tool (assuming the
OpenTracing C Wrapper library is installed in the /opt directory):
% PKG_CONFIG_PATH=/opt/lib/pkgconfig make USE_OT=1 TARGET=linux-glibc
The OT filter can also be compiled in debug mode as follows:
% PKG_CONFIG_PATH=/opt/lib/pkgconfig make USE_OT=1 OT_DEBUG=1 TARGET=linux-glibc
HAProxy compilation example explicitly specifying path to the OpenTracing C
Wrapper include and library:
% make USE_OT=1 OT_INC=/opt/include OT_LIB=/opt/lib TARGET=linux-glibc
In case we want to use debug mode, then it looks like this:
% make USE_OT=1 OT_DEBUG=1 OT_INC=/opt/include OT_LIB=/opt/lib TARGET=linux-glibc
If the library we want to use is not installed on a unix system, then a locally
installed library can be used (say, which is compiled and installed in the user
home directory). In this case instead of /opt/include and /opt/lib the
equivalent paths to the local installation should be specified. Of course,
in that case the pkg-config tool can also be used if we have a complete
installation (with .pc files).
last but not least, if the pkg-config tool is not used when compiling, then
HAProxy executable may not be able to find the OpenTracing C Wrapper library
at startup. This can be solved in several ways, for example using the
LD_LIBRARY_PATH environment variable which should be set to the path where the
library is located before starting the HAProxy.
% LD_LIBRARY_PATH=/opt/lib /path-to/haproxy ...
Another way is to add RUNPATH to HAProxy executable that contains the path to
the library in question.
% make USE_OT=1 OT_RUNPATH=1 OT_INC=/opt/include OT_LIB=/opt/lib TARGET=linux-glibc
After HAProxy is compiled, we can check if the OT filter is enabled:
% ./haproxy -vv | grep opentracing
--- command output ----------
[ OT] opentracing
--- command output ----------
3. Basic concepts in OpenTracing
---------------------------------
Basic concepts of OpenTracing can be read on the OpenTracing documentation
website https://opentracing.io/docs/overview/.
Here we will list only the most important elements of distributed tracing and
these are 'trace', 'span' and 'span context'. Trace is a description of the
complete transaction we want to record in the tracing system. A span is an
operation that represents a unit of work that is recorded in a tracing system.
Span context is a group of information related to a particular span that is
passed on to the system (from service to service). Using this context, we can
add new spans to already open trace (or supplement data in already open spans).
An individual span may contain one or more tags, logs and baggage items.
The tag is a key-value element that is valid for the entire span. Log is a
key-value element that allows you to write some data at a certain time, it
can be used for debugging. A baggage item is a key-value data pair that can
be used for the duration of an entire trace, from the moment it is added to
the span.
4. OT configuration
--------------------
In order for the OT filter to be used, it must be included in the HAProxy
configuration, in the proxy section (frontend / listen / backend):
frontend ot-test
...
filter opentracing [id <id>] config <file>
...
If no filter id is specified, 'ot-filter' is used as default. The 'config'
parameter must be specified and it contains the path of the file used to
configure the OT filter.
4.1 OT scope
-------------
If the filter id is defined for the OT filter, then the OT scope with
the same name should be defined in the configuration file. In the same
configuration file we can have several defined OT scopes.
Each OT scope must have a defined (only one) "ot-tracer" section that is
used to configure the operation of the OT filter and define the used groups
and scopes.
OT scope starts with the id of the filter specified in square brackets and
ends with the end of the file or when a new OT scope is defined.
For example, this defines two OT scopes in the same configuration file:
[my-first-ot-filter]
ot-tracer tracer1
...
ot-group group1
...
ot-scope scope1
...
[my-second-ot-filter]
...
4.2. "ot-tracer" section
-------------------------
Only one "ot-tracer" section must be defined for each OT scope.
There are several keywords that must be defined for the OT filter to work.
These are 'config' which defines the configuration file for the OpenTracing
API, and 'plugin' which defines the OpenTracing plugin used.
Through optional keywords can be defined ACLs, logging, rate limit, and groups
and scopes that define the tracing model.
ot-tracer <name>
A new OT with the name <name> is created.
Arguments :
name - the name of the tracer section
The following keywords are supported in this section:
- mandatory keywords:
- config
- plugin
- optional keywords:
- acl
- debug-level
- groups
- [no] log
- [no] option disabled
- [no] option dontlog-normal
- [no] option hard-errors
- rate-limit
- scopes
acl <aclname> <criterion> [flags] [operator] <value> ...
Declare or complete an access list.
To configure and use the ACL, see section 7 of the HAProxy Configuration
Manual.
config <file>
'config' is one of the two mandatory keywords associated with the OT tracer
configuration. This keyword sets the path of the configuration file for the
OpenTracing tracer plugin. To set the contents of this configuration file,
it is best to look at the documentation related to the OpenTracing tracer we
want to use.
Arguments :
file - the path of the configuration file
debug-level <value>
This keyword sets the value of the debug level related to the display of
debug messages in the OT filter. The 'debug-level' value is binary, ie
a single value bit enables or disables the display of the corresponding
debug message that uses that bit. The default value is set via the
FLT_OT_DEBUG_LEVEL macro in the include/config.h file. Debug level value
is used only if the OT filter is compiled with the debug mode enabled,
otherwise it is ignored.
Arguments :
value - binary value ranging from 0 to 255 (8 bits)
groups <name> ...
A list of "ot-group" groups used for the currently defined tracer is declared.
Several groups can be specified in one line.
Arguments :
name - the name of the OT group
log global
log <addr> [len <len>] [format <fmt>] <facility> [<level> [<minlevel>]]
no log
Enable per-instance logging of events and traffic.
To configure and use the logging system, see section 4.2 of the HAProxy
Configuration Manual.
option disabled
no option disabled
Keyword which turns the operation of the OT filter on or off. By default
the filter is on.
option dontlog-normal
no option dontlog-normal
Enable or disable logging of normal, successful processing. By default,
this option is disabled. For this option to be considered, logging must
be turned on.
See also: 'log' keyword description.
option hard-errors
no option hard-errors
During the operation of the filter, some errors may occur, caused by
incorrect configuration of the tracer or some error related to the operation
of HAProxy. By default, such an error will not interrupt the filter
operation for the stream in which the error occurred. If the 'hard-error'
option is enabled, the operation error prohibits all further processing of
events and groups in the stream in which the error occurred.
plugin <file>
'plugin' is one of the two mandatory keywords associated with the OT tracer
configuration. This keyword sets the path of the OpenTracing tracer plugin.
Arguments :
file - the name of the plugin used
rate-limit <value>
This option allows limiting the use of the OT filter, ie it can be influenced
whether the OT filter is activated for a stream or not. Determining whether
or not a filter is activated depends on the value of this option that is
compared to a randomly selected value when attaching the filter to the stream.
By default, the value of this option is set to 100.0, ie the OT filter is
activated for each stream.
Arguments :
value - floating point value ranging from 0.0 to 100.0
scopes <name> ...
This keyword declares a list of "ot-scope" definitions used for the currently
defined tracer. Multiple scopes can be specified in the same line.
Arguments :
name - the name of the OT scope
4.3. "ot-scope" section
------------------------
Stream processing begins with filter attachment, then continues with the
processing of a number of defined events and groups, and ends with filter
detachment. The "ot-scope" section is used to define actions related to
individual events. However, this section may be part of a group, so the
event does not have to be part of the definition.
ot-scope <name>
Creates a new OT scope definition named <name>.
Arguments :
name - the name of the OT scope
The following keywords are supported in this section:
- acl
- baggage
- event
- extract
- finish
- inject
- log
- span
- tag
acl <aclname> <criterion> [flags] [operator] <value> ...
Declare or complete an access list.
To configure and use the ACL, see section 7 of the HAProxy Configuration
Manual.
baggage <name> <sample> ...
Baggage items allow the propagation of data between spans, ie allow the
assignment of metadata that is propagated to future children spans.
This data is formatted in the style of key-value pairs and is part of
the context that can be transferred between processes that are part of
a server architecture.
This kewyord allows setting the baggage for the currently active span. The
data type is always a string, ie any sample type is converted to a string.
The exception is a binary value that is not supported by the OT filter.
See the 'tag' keyword description for the data type conversion table.
Arguments :
name - key part of a data pair
sample - sample expression (value part of a data pair), at least one
sample must be present
event <name> [{ if | unless } <condition>]
Set the event that triggers the 'ot-scope' to which it is assigned.
Optionally, it can be followed by an ACL-based condition, in which case it
will only be evaluated if the condition is true.
ACL-based conditions are executed in the context of a stream that processes
the client and server connections. To configure and use the ACL, see
section 7 of the HAProxy Configuration Manual.
Arguments :
name - the event name
condition - a standard ACL-based condition
Supported events are (the table gives the names of the events in the OT
filter and the corresponding equivalent in the SPOE filter):
-------------------------------------|------------------------------
the OT filter | the SPOE filter
-------------------------------------|------------------------------
on-client-session-start | on-client-session
on-frontend-tcp-request | on-frontend-tcp-request
on-http-wait-request | -
on-http-body-request | -
on-frontend-http-request | on-frontend-http-request
on-switching-rules-request | -
on-backend-tcp-request | on-backend-tcp-request
on-backend-http-request | on-backend-http-request
on-process-server-rules-request | -
on-http-process-request | -
on-tcp-rdp-cookie-request | -
on-process-sticking-rules-request | -
on-client-session-end | -
on-server-unavailable | -
-------------------------------------|------------------------------
on-server-session-start | on-server-session
on-tcp-response | on-tcp-response
on-http-wait-response | -
on-process-store-rules-response | -
on-http-response | on-http-response
on-server-session-end | -
-------------------------------------|------------------------------
extract <name-prefix> [use-vars | use-headers]
For a more detailed description of the propagation process of the span
context, see the description of the keyword 'inject'. Only the process
of extracting data from the carrier is described here.
Arguments :
name-prefix - data name prefix (ie key element prefix)
use-vars - data is extracted from HAProxy variables
use-headers - data is extracted from the HTTP header
Below is an example of using HAProxy variables to transfer span context data:
--- test/ctx/ot.cfg --------------------------------------------------------
...
ot-scope client_session_start_2
extract "ot_ctx_1" use-vars
span "Client session" child-of "ot_ctx_1"
...
----------------------------------------------------------------------------
finish <name> ...
Closing a particular span or span context. Instead of the name of the span,
there are several specially predefined names with which we can finish certain
groups of spans. So it can be used as the name '*req*' for all open spans
related to the request channel, '*res*' for all open spans related to the
response channel and '*' for all open spans regardless of which channel they
are related to. Several spans and/or span contexts can be specified in one
line.
Arguments :
name - the name of the span or context context
inject <name-prefix> [use-vars] [use-headers]
In OpenTracing, the transfer of data related to the tracing process between
microservices that are part of a larger service is done through the
propagation of the span context. The basic operations that allow us to
access and transfer this data are 'inject' and 'extract'.
'inject' allows us to extract span context so that the obtained data can
be forwarded to another process (microservice) via the selected carrier.
'inject' in the name actually means inject data into carrier. Carrier is
an interface here (ie a data structure) that allows us to transfer tracing
state from one process to another.
Data transfer can take place via one of two selected storage methods, the
first is by adding data to the HTTP header and the second is by using HAProxy
variables. Only data transfer via HTTP header can be used to transfer data
to another process (ie microservice). All data is organized in the form of
key-value data pairs.
No matter which data transfer method you use, we need to specify a prefix
for the key element. All alphanumerics (lowercase only) and underline
character can be used to construct the data name prefix. Uppercase letters
can actually be used, but they will be converted to lowercase when creating
the prefix.
Arguments :
name-prefix - data name prefix (ie key element prefix)
use-vars - HAProxy variables are used to store and transfer data
use-headers - HTTP headers are used to store and transfer data
Below is an example of using HTTP headers and variables, and how this is
reflected in the internal data of the HAProxy process.
--- test/ctx/ot.cfg --------------------------------------------------------
...
ot-scope client_session_start_1
span "HAProxy session" root
inject "ot_ctx_1" use-headers use-vars
...
----------------------------------------------------------------------------
- generated HAProxy variable (key -> value):
txn.ot_ctx_1.uberDtraceDid -> 8f1a05a3518d2283:8f1a05a3518d2283:0:1
- generated HTTP header (key: value):
ot_ctx_1-uber-trace-id: 8f1a05a3518d2283:8f1a05a3518d2283:0:1
Because HAProxy does not allow the '-' character in the variable name (which
is automatically generated by the OpenTracing API and on which we have no
influence), it is converted to the letter 'D'. We can see that there is no
such conversion in the name of the HTTP header because the '-' sign is allowed
there. Due to this conversion, initially all uppercase letters are converted
to lowercase because otherwise we would not be able to distinguish whether
the disputed sign '-' is used or not.
Thus created HTTP headers and variables are deleted when executing the
'finish' keyword or when detaching the stream from the filter.
log <name> <sample> ...
This kewyord allows setting the log for the currently active span. The
data type is always a string, ie any sample type is converted to a string.
The exception is a binary value that is not supported by the OT filter.
See the 'tag' keyword description for the data type conversion table.
Arguments :
name - key part of a data pair
sample - sample expression (value part of a data pair), at least one
sample must be present
span <name> [<reference>]
Creating a new span (or referencing an already opened one). If a new span
is created, it can be a child of the referenced span, follow from the
referenced span, or be root 'span'. In case we did not specify a reference
to the previously created span, the new span will become the root span.
We need to pay attention to the fact that in one trace there can be only
one root span. In case we have specified a non-existent span as a reference,
a new span will not be created.
Arguments :
name - the name of the span being created or referenced (operation
name)
reference - span or span context to which the created span is referenced
tag <name> <sample> ...
This kewyord allows setting a tag for the currently active span. The first
argument is the name of the tag (tag ID) and the second its value. A value
can consist of one or more data. If the value is only one data, then the
type of that data depends on the type of the HAProxy sample. If the value
contains more data, then the data type is string. The data conversion table
is below:
HAProxy sample data type | the OpenTracing data type
--------------------------+---------------------------
NULL | NULL
BOOL | BOOL
INT32 | INT64
UINT32 | UINT64
INT64 | INT64
UINT64 | UINT64
IPV4 | STRING
IPV6 | STRING
STRING | STRING
BINARY | UNSUPPORTED
--------------------------+---------------------------
Arguments :
name - key part of a data pair
sample - sample expression (value part of a data pair), at least one
sample must be present
4.4. "ot-group" section
------------------------
This section allows us to define a group of OT scopes, that is not activated
via an event but is triggered from TCP or HTTP rules. More precisely, these
are the following rules: 'tcp-request', 'tcp-response', 'http-request',
'http-response' and 'http-after-response'. These rules can be defined in the
HAProxy configuration file.
ot-group <name>
Creates a new OT group definition named <name>.
Arguments :
name - the name of the OT group
The following keywords are supported in this section:
- scopes
scopes <name> ...
'ot-scope' sections that are part of the specified group are defined. If
the mentioned 'ot-scope' sections are used only in some OT group, they do
not have to have defined events. Several 'ot-scope' sections can be
specified in one line.
Arguments :
name - the name of the 'ot-scope' section
5. Examples
------------
Several examples of the OT filter configuration can be found in the test
directory. A brief description of the prepared configurations follows:
cmp - the configuration very similar to that of the spoa-opentracing project.
It was made to compare the speed of the OT filter with the
implementation of distributed tracing via spoa-opentracing application.
sa - the configuration in which all possible events are used.
ctx - the configuration is very similar to the previous one, with the only
difference that the spans are opened using the span context as a span
reference.
fe be - a slightly more complicated example of the OT filter configuration
that uses two cascaded HAProxy services. The span context between
HAProxy processes is transmitted via the HTTP header.
empty - the empty configuration in which the OT filter is initialized but
no event is triggered. It is not very usable, except to check the
behavior of the OT filter in the case of a similar configuration.
In order to be able to collect data (and view results via the web interface)
we need to install some of the supported tracers. We will use the Jaeger
tracer as an example. Installation instructions can be found on the website
https://www.jaegertracing.io/download/. For the impatient, here we will list
how the image to test the operation of the tracer system can be installed
without much reading of the documentation.
# docker pull jaegertracing/all-in-one:latest
# docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 \
-p 16686:16686 -p 14268:14268 -p 9411:9411 jaegertracing/all-in-one:latest
The last command will also initialize and run the Jaeger container. If we
want to use that container later, it can be started and stopped in the classic
way, using the 'docker container start/stop' commands.
In order to be able to use any of the configurations from the test directory,
we must also have a tracer plugin in that directory (all examples use the
Jaeger tracer plugin). The simplest way is to download the tracer plugin
using the already prepared shell script get-opentracing-plugins.sh.
The script accepts one argument, the directory in which the download is made.
If run without an argument, the script downloads all plugins to the current
directory.
% ./get-opentracing-plugins.sh
After that, we can run one of the pre-configured configurations using the
provided script run-xxx.sh (where xxx is the name of the configuration being
tested). For example:
% ./run-sa.sh
The script will create a new log file each time it is run (because part of the
log file name is the start time of the script).
Eh, someone will surely notice that all test configurations use the Jaeger
tracing plugin that cannot be downloaded using the get-opentracing-plugins.sh
script. Unfortunately, the latest precompiled version that can be downloaded
is 0.4.2, for newer ones only the source code can be found. Version 0.4.2 has
a bug that can cause the operation of the OT filter to get stuck, so it is
better not to use this version. Here is the procedure by which we can compile
a newer version of the plugin (in our example it is 0.5.0).
Important note: the GCC version must be at least 4.9 or later.
% wget https://github.com/jaegertracing/jaeger-client-cpp/archive/v0.5.0.tar.gz
% tar xf v0.5.0.tar.gz
% cd jaeger-client-cpp-0.5.0
% mkdir build
% cd build
% cmake -DCMAKE_INSTALL_PREFIX=/opt -DJAEGERTRACING_PLUGIN=ON -DHUNTER_CONFIGURATION_TYPES=Release -DHUNTER_BUILD_SHARED_LIBS=OFF ..
% make
After the plugin is compiled, it will be in the current directory. The name
of the plugin is libjaegertracing_plugin.so.
5.1. Benchmarking results
--------------------------
To check the operation of the OT filter, several different test configurations
have been made which are located in the test directory. The test results of
the same configurations (with the names README-speed-xxx, where xxx is the name
of the configuration being tested) are also in the directory of the same name.
All tests were performed on the same debian 9.13 system, CPU i7-4770, 32 GB RAM.
For the purpose of testing, the thttpd web server on port 8000 was used.
Testing was done with the wrk utility running via run-xxx.sh scripts; that is,
via the test-speed.sh script that is run as follows:
% ./test-speed.sh all
The above mentioned thttpd web server is run from that script and it should be
noted that we need to have the same installed on the system (or change the path
to the thttpd server in that script if it is installed elsewhere).
Each test is performed several times over a period of 5 minutes per individual
test. The only difference when running the tests for the same configuration
was in changing the 'rate-limit' parameter (and the 'option disabled' option),
which is set to the following values: 100.0, 50.0, 10.0, 2.5 and 0.0 percent.
Then a test is performed with the OT filter active but disabled for request
processing ('option disabled' is included in the ot.cfg configuration). In
the last test, the OT filter is not used at all, ie it is not active and does
not affect the operation of HAProxy in any way.
6. OT CLI
----------
Via the HAProxy CLI interface we can find out the current status of the OT
filter and change several of its settings.
All supported CLI commands can be found in the following way, using the
socat utility with the assumption that the HAProxy CLI socket path is set
to /tmp/haproxy.sock (of course, instead of socat, nc or other utility can
be used with a change in arguments when running the same):
% echo "help" | socat - UNIX-CONNECT:/tmp/haproxy.sock | grep flt-ot
--- command output ----------
flt-ot debug [level] : set the OT filter debug level (default: get current debug level)
flt-ot disable : disable the OT filter
flt-ot enable : enable the OT filter
flt-ot soft-errors : turning off hard-errors mode
flt-ot hard-errors : enabling hard-errors mode
flt-ot logging [state] : set logging state (default: get current logging state)
flt-ot rate [value] : set the rate limit (default: get current rate value)
flt-ot status : show the OT filter status
--- command output ----------
'flt-ot debug' can only be used in case the OT filter is compiled with the
debug mode enabled.
7. Known bugs and limitations
------------------------------
The name of the span context definition can contain only letters, numbers and
characters '_' and '-'. Also, all uppercase letters in the name are converted
to lowercase. The character '-' is converted internally to the 'D' character,
and since a HAProxy variable is generated from that name, this should be taken
into account if we want to use it somewhere in the HAProxy configuration.
The above mentioned span context is used in the 'inject' and 'extract' keywords.
Let's look a little at the example test/fe-be (configurations are in the
test/fe and test/be directories, 'fe' is here the abbreviation for frontend
and 'be' for backend). In case we have the 'rate-limit' set to a value less
than 100.0, then distributed tracing will not be started with each new HTTP
request. It also means that the span context will not be delivered (via the
HTTP header) to the backend HAProxy process. The 'rate-limit' on the backend
HAProxy must be set to 100.0, but because the frontend HAProxy does not send
a span context every time, all such cases will cause an error to be reported
on the backend server. Therefore, the 'hard-errors' option must be set on the
backend server, so that processing on that stream is stopped as soon as the
first error occurs. Such cases will slow down the backend server's response
a bit (in the example in question it is about 3%).

View file

@ -1,298 +0,0 @@
Here I will write down some specifics of certain parts of the source, these are
just some of my thoughts and clues and they are probably not too important for
a wider audience.
src/parser.c
------------------------------------------------------------------------------
The first thing to run when starting the HAProxy is the flt_ot_parse() function
which actually parses the filter configuration.
In case of correct configuration, the function returns ERR_NONE (or 0), while
in case of incorrect configuration it returns the combination of ERR_* flags
(ERR_NONE here does not belong to that bit combination because its value is 0).
One of the parameters of the function is <char **err> in which an error message
can be returned, if it exists. In that case the return value of the function
should have some of the ERR_* flags set.
Let's look at an example of the following filter configuration what the function
call sequence looks like.
Filter configuration line:
filter opentracing [id <id>] config <file>
Function call sequence:
flt_ot_parse(<err>) {
/* Initialization of the filter configuration data. */
flt_ot_conf_init() {
}
/* Setting the filter name. */
flt_ot_parse_keyword(<err>) {
flt_ot_parse_strdup(<err>) {
}
}
/* Setting the filter configuration file name. */
flt_ot_parse_keyword(<err>) {
flt_ot_parse_strdup(<err>) {
}
}
/* Checking the configuration of the filter. */
flt_ot_parse_cfg(<err>) {
flt_ot_parse_cfg_tracer() {
}
...
flt_ot_post_parse_cfg_tracer() {
}
flt_ot_parse_cfg_group() {
}
...
flt_ot_post_parse_cfg_group() {
}
flt_ot_parse_cfg_scope() {
}
...
flt_ot_post_parse_cfg_scope() {
}
}
}
Checking the filter configuration is actually much more complicated, only the
name of the main function flt_ot_parse_cfg() that does it is listed here.
All functions that use the <err> parameter should set the error status using
that pointer. All other functions (actually these are all functions called
by the flt_ot_parse_cfg() function) should set the error message using the
ha_warning()/ha_alert() HAProxy functions. Of course, the return value (the
mentioned combination of ERR_* bits) is set in all these functions and it
indicates whether the filter configuration is correct or not.
src/group.c
------------------------------------------------------------------------------
The OT filter allows the use of groups within which one or more 'ot-scope'
declarations can be found. These groups can be used using several HAProxy
rules, more precisely 'http-request', 'http-response', 'tcp-request',
'tcp-response' and 'http-after-response' rules.
Configuration example for the specified rules:
<rule> ot-group <filter-id> <group-name> [ { if | unless } <condition> ]
Parsing each of these rules is performed by the flt_ot_group_parse() function.
After parsing the configuration, its verification is performed via the
flt_ot_group_check() function. One parsing function and one configuration
check function are called for each defined rule.
flt_ot_group_parse(<err>) {
}
...
flt_ot_group_check() {
}
...
When deinitializing the module, the function flt_ot_group_release() is called
(which is actually an release_ptr callback function from one of the above
rules). One callback function is called for each defined rule.
flt_ot_group_release() {
}
...
src/filter.c
------------------------------------------------------------------------------
After parsing and checking the configuration, the flt_ot_check() function is
called which associates the 'ot-group' and 'ot-scope' definitions with their
declarations. This procedure concludes the configuration of the OT filter and
after that its initialization is possible.
flt_ops.check = flt_ot_check;
flt_ot_check() {
}
The initialization of the OT filter is done via the flt_ot_init() callback
function. In this function the OpenTracing API library is also initialized.
It is also possible to initialize for each thread individually, but nothing
is being done here for now.
flt_ops.init = flt_ot_init;
flt_ot_init() {
flt_ot_cli_init() {
}
/* Initialization of the OpenTracing API. */
ot_init(<err>) {
}
}
flt_ops.init_per_thread = flt_ot_init_per_thread;
flt_ot_init_per_thread() {
}
...
After the filter instance is created and attached to the stream, the
flt_ot_attach() function is called. In this function a new OT runtime
context is created, and flags are set that define which analyzers are used.
flt_ops.attach = flt_ot_attach;
flt_ot_attach() {
/* In case OT is disabled, nothing is done on this stream further. */
flt_ot_runtime_context_init(<err>) {
flt_ot_pool_alloc() {
}
/* Initializing and setting the variable 'sess.ot.uuid'. */
if (flt_ot_var_register(<err>) != -1) {
flt_ot_var_set(<err>) {
}
}
}
}
When a stream is started, this function is called. At the moment, nothing
is being done in it.
flt_ops.stream_start = flt_ot_stream_start;
flt_ot_stream_start() {
}
Channel analyzers are called when executing individual filter events.
For each of the four analyzer functions, the events associated with them
are listed.
Events:
- 1 'on-client-session-start'
- 15 'on-server-session-start'
------------------------------------------------------------------------
flt_ops.channel_start_analyze = flt_ot_channel_start_analyze;
flt_ot_channel_start_analyze() {
flt_ot_event_run() {
/* Run event. */
flt_ot_scope_run() {
/* Processing of all ot-scopes defined for the current event. */
}
}
}
Events:
- 2 'on-frontend-tcp-request'
- 4 'on-http-body-request'
- 5 'on-frontend-http-request'
- 6 'on-switching-rules-request'
- 7 'on-backend-tcp-request'
- 8 'on-backend-http-request'
- 9 'on-process-server-rules-request'
- 10 'on-http-process-request'
- 11 'on-tcp-rdp-cookie-request'
- 12 'on-process-sticking-rules-request
- 16 'on-tcp-response'
- 18 'on-process-store-rules-response'
- 19 'on-http-response'
------------------------------------------------------------------------
flt_ops.channel_pre_analyze = flt_ot_channel_pre_analyze;
flt_ot_channel_pre_analyze() {
flt_ot_event_run() {
/* Run event. */
flt_ot_scope_run() {
/* Processing of all ot-scopes defined for the current event. */
}
}
}
Events:
- 3 'on-http-wait-request'
- 17 'on-http-wait-response'
------------------------------------------------------------------------
flt_ops.channel_post_analyze = flt_ot_channel_post_analyze;
flt_ot_channel_post_analyze() {
flt_ot_event_run() {
/* Run event. */
flt_ot_scope_run() {
/* Processing of all ot-scopes defined for the current event. */
}
}
}
Events:
- 13 'on-client-session-end'
- 14 'on-server-unavailable'
- 20 'on-server-session-end'
------------------------------------------------------------------------
flt_ops.channel_end_analyze = flt_ot_channel_end_analyze;
flt_ot_channel_end_analyze() {
flt_ot_event_run() {
/* Run event. */
flt_ot_scope_run() {
/* Processing of all ot-scopes defined for the current event. */
}
}
/* In case the backend server does not work, event 'on-server-unavailable'
is called here before event 'on-client-session-end'. */
if ('on-server-unavailable') {
flt_ot_event_run() {
/* Run event. */
flt_ot_scope_run() {
/* Processing of all ot-scopes defined for the current event. */
}
}
}
}
After the stream has stopped, this function is called. At the moment, nothing
is being done in it.
flt_ops.stream_stop = flt_ot_stream_stop;
flt_ot_stream_stop() {
}
Then, before the instance filter is detached from the stream, the following
function is called. It deallocates the runtime context of the OT filter.
flt_ops.detach = flt_ot_detach;
flt_ot_detach() {
flt_ot_runtime_context_free() {
flt_ot_pool_free() {
}
}
}
Module deinitialization begins with deinitialization of individual threads
(as many threads as configured for the HAProxy process). Because nothing
special is connected to the process threads, nothing is done in this function.
flt_ops.deinit_per_thread = flt_ot_deinit_per_thread;
flt_ot_deinit_per_thread() {
}
...
For this function see the above description related to the src/group.c file.
flt_ot_group_release() {
}
...
Module deinitialization ends with the flt_ot_deinit() function, in which all
memory occupied by module operation (and OpenTracing API operation, of course)
is freed.
flt_ops.deinit = flt_ot_deinit;
flt_ot_deinit() {
ot_close() {
}
flt_ot_conf_free() {
}
}

View file

@ -1,25 +0,0 @@
Used pools:
-------------------------------+-----------------------------+-----------------------------
head / name | size | define
-------------------------------+-----------------------------+-----------------------------
pool_head_ buffer | global.tune.bufsize = 16384 | USE_POOL_BUFFER
pool_head_ trash | 32 + 16384 | USE_TRASH_CHUNK
-------------------------------+-----------------------------+-----------------------------
pool_head_ ot_scope_span | 96 | USE_POOL_OT_SCOPE_SPAN
pool_head_ ot_scope_context | 64 | USE_POOL_OT_SCOPE_CONTEXT
pool_head_ ot_runtime_context | 128 | USE_POOL_OT_RUNTIME_CONTEXT
pool_head_ ot_span_context | 96 | USE_POOL_OT_SPAN_CONTEXT
-------------------------------+-----------------------------+-----------------------------
By defining individual definitions in file include/config.h, it is possible to
switch individual pools on / off. If a particular pool is not used, memory is
used in a 'normal' way instead, using malloc()/free() functions.
This is made only from the aspect of debugging the program, i.e. comparing the
speed of operation using different methods of working with memory.
In general, it would be better to use memory pools, due to less fragmentation
of memory space after long operation of the program. The speed of operation
is similar to when using standard allocation functions (when testing it was
shown that pool use was fast by about 1%).

View file

@ -1,50 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_CLI_H_
#define _OPENTRACING_CLI_H_
#define FLT_OT_CLI_CMD "flt-ot"
#define FLT_OT_CLI_LOGGING_OFF "off"
#define FLT_OT_CLI_LOGGING_ON "on"
#define FLT_OT_CLI_LOGGING_NOLOGNORM "dontlog-normal"
#define FLT_OT_CLI_LOGGING_STATE(a) ((a) & FLT_OT_LOGGING_ON) ? (((a) & FLT_OT_LOGGING_NOLOGNORM) ? "enabled, " FLT_OT_CLI_LOGGING_NOLOGNORM : "enabled") : "disabled"
#define FLT_OT_CLI_MSG_CAT(a) ((a) == NULL) ? "" : (a), ((a) == NULL) ? "" : "\n"
enum FLT_OT_LOGGING_enum {
FLT_OT_LOGGING_OFF = 0,
FLT_OT_LOGGING_ON = 1 << 0,
FLT_OT_LOGGING_NOLOGNORM = 1 << 1,
};
void flt_ot_cli_init(void);
#endif /* _OPENTRACING_CLI_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,228 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_CONF_H_
#define _OPENTRACING_CONF_H_
#define FLT_OT_CONF(f) ((struct flt_ot_conf *)FLT_CONF(f))
#define FLT_OT_CONF_HDR_FMT "%p:{ { '%.*s' %zu %d } "
#define FLT_OT_CONF_HDR_ARGS(a,b) (a), (int)(a)->b##_len, (a)->b, (a)->b##_len, (a)->cfg_line
#define FLT_OT_STR_HDR_ARGS(a,b) (a)->b, (a)->b##_len
#define FLT_OT_DBG_CONF_SAMPLE_EXPR(f,a) \
FLT_OT_DBG(3, "%s%p:{ '%s' %p }", (f), (a), (a)->value, (a)->expr)
#define FLT_OT_DBG_CONF_SAMPLE(f,a) \
FLT_OT_DBG(3, "%s%p:{ '%s' '%s' %s %d }", \
(f), (a), (a)->key, (a)->value, flt_ot_list_debug(&((a)->exprs)), (a)->num_exprs)
#define FLT_OT_DBG_CONF_STR(f,a) \
FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "}", FLT_OT_CONF_HDR_ARGS(a, str))
#define FLT_OT_DBG_CONF_CONTEXT(f,a) \
FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "0x%02hhx }", FLT_OT_CONF_HDR_ARGS(a, id), (a)->flags)
#define FLT_OT_DBG_CONF_SPAN(f,a) \
FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "'%s' %zu %d '%s' %zu %hhu 0x%02hhx %s %s %s }", \
FLT_OT_CONF_HDR_ARGS(a, id), FLT_OT_STR_HDR_ARGS(a, ref_id), (a)->ref_type, \
FLT_OT_STR_HDR_ARGS(a, ctx_id), (a)->flag_root, (a)->ctx_flags, flt_ot_list_debug(&((a)->tags)), \
flt_ot_list_debug(&((a)->logs)), flt_ot_list_debug(&((a)->baggages)))
#define FLT_OT_DBG_CONF_SCOPE(f,a) \
FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "%hhu %d %s %p %s %s %s }", \
FLT_OT_CONF_HDR_ARGS(a, id), (a)->flag_used, (a)->event, flt_ot_list_debug(&((a)->acls)), \
(a)->cond, flt_ot_list_debug(&((a)->contexts)), flt_ot_list_debug(&((a)->spans)), \
flt_ot_list_debug(&((a)->finish)))
#define FLT_OT_DBG_CONF_GROUP(f,a) \
FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "%hhu %s }", \
FLT_OT_CONF_HDR_ARGS(a, id), (a)->flag_used, flt_ot_list_debug(&((a)->ph_scopes)))
#define FLT_OT_DBG_CONF_PH(f,a) \
FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "%p }", FLT_OT_CONF_HDR_ARGS(a, id), (a)->ptr)
#define FLT_OT_DBG_CONF_TRACER(f,a) \
FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "'%s' %p '%s' %p %u %hhu %hhu 0x%02hhx %p:%s 0x%08x %s %s %s }", \
FLT_OT_CONF_HDR_ARGS(a, id), (a)->config, (a)->cfgbuf, (a)->plugin, (a)->tracer, (a)->rate_limit, (a)->flag_harderr, \
(a)->flag_disabled, (a)->logging, &((a)->proxy_log), flt_ot_list_debug(&((a)->proxy_log.loggers)), (a)->analyzers, \
flt_ot_list_debug(&((a)->acls)), flt_ot_list_debug(&((a)->ph_groups)), flt_ot_list_debug(&((a)->ph_scopes)))
#define FLT_OT_DBG_CONF(f,a) \
FLT_OT_DBG(3, "%s%p:{ %p '%s' '%s' %p %s %s }", \
(f), (a), (a)->proxy, (a)->id, (a)->cfg_file, (a)->tracer, \
flt_ot_list_debug(&((a)->groups)), flt_ot_list_debug(&((a)->scopes)))
#define FLT_OT_STR_HDR(a) \
struct { \
char *a; \
size_t a##_len; \
}
#define FLT_OT_CONF_HDR(a) \
struct { \
FLT_OT_STR_HDR(a); \
int cfg_line; \
struct list list; \
}
struct flt_ot_conf_hdr {
FLT_OT_CONF_HDR(id);
};
/* flt_ot_conf_sample->exprs */
struct flt_ot_conf_sample_expr {
FLT_OT_CONF_HDR(value); /* The sample value. */
struct sample_expr *expr; /* The sample expression. */
};
/*
* flt_ot_conf_span->tags
* flt_ot_conf_span->logs
* flt_ot_conf_span->baggages
*/
struct flt_ot_conf_sample {
FLT_OT_CONF_HDR(key); /* The sample name. */
char *value; /* The sample content. */
struct list exprs; /* Used to chain sample expressions. */
int num_exprs; /* Number of defined expressions. */
};
/* flt_ot_conf_scope->finish */
struct flt_ot_conf_str {
FLT_OT_CONF_HDR(str); /* String content/length. */
};
/* flt_ot_conf_scope->contexts */
struct flt_ot_conf_context {
FLT_OT_CONF_HDR(id); /* The name of the context. */
uint8_t flags; /* The type of storage from which the span context is extracted. */
};
/* flt_ot_conf_scope->spans */
struct flt_ot_conf_span {
FLT_OT_CONF_HDR(id); /* The name of the span. */
FLT_OT_STR_HDR(ref_id); /* The reference name, if used. */
int ref_type; /* The reference type. */
FLT_OT_STR_HDR(ctx_id); /* The span context name, if used. */
uint8_t ctx_flags; /* The type of storage used for the span context. */
bool flag_root; /* Whether this is a root span. */
struct list tags; /* The set of key:value tags. */
struct list logs; /* The set of key:value logs. */
struct list baggages; /* The set of key:value baggage items. */
};
struct flt_ot_conf_scope {
FLT_OT_CONF_HDR(id); /* The scope name. */
bool flag_used; /* The indication that the scope is being used. */
int event; /* FLT_OT_EVENT_* */
struct list acls; /* ACLs declared on this scope. */
struct acl_cond *cond; /* ACL condition to meet. */
struct list contexts; /* Declared contexts. */
struct list spans; /* Declared spans. */
struct list finish; /* The list of spans to be finished. */
};
struct flt_ot_conf_group {
FLT_OT_CONF_HDR(id); /* The group name. */
bool flag_used; /* The indication that the group is being used. */
struct list ph_scopes; /* List of all used scopes. */
};
struct flt_ot_conf_ph {
FLT_OT_CONF_HDR(id); /* The scope/group name. */
void *ptr; /* Pointer to real placeholder structure. */
};
#define flt_ot_conf_ph_group flt_ot_conf_ph
#define flt_ot_conf_ph_scope flt_ot_conf_ph
struct flt_ot_conf_tracer {
FLT_OT_CONF_HDR(id); /* The tracer name. */
char *config; /* The OpenTracing configuration file name. */
char *cfgbuf; /* The OpenTracing configuration. */
char *plugin; /* The OpenTracing plugin library file name. */
struct otc_tracer *tracer; /* The OpenTracing tracer handle. */
uint32_t rate_limit; /* [0 2^32-1] <-> [0.0 100.0] */
bool flag_harderr; /* [0 1] */
bool flag_disabled; /* [0 1] */
uint8_t logging; /* [0 1 3] */
struct proxy proxy_log; /* The log server list. */
uint analyzers; /* Defined channel analyzers. */
struct list acls; /* ACLs declared on this tracer. */
struct list ph_groups; /* List of all used groups. */
struct list ph_scopes; /* List of all used scopes. */
};
struct flt_ot_counters {
#ifdef DEBUG_OT
struct {
bool flag_used; /* Whether this event is used. */
uint64_t htx[2]; /* htx_is_empty() function result counter. */
} event[FLT_OT_EVENT_MAX];
#endif
uint64_t disabled[2]; /* How many times stream processing is disabled. */
};
/* The OpenTracing filter configuration. */
struct flt_ot_conf {
struct proxy *proxy; /* Proxy owning the filter. */
char *id; /* The OpenTracing filter id. */
char *cfg_file; /* The OpenTracing filter configuration file name. */
struct flt_ot_conf_tracer *tracer; /* There can only be one tracer. */
struct list groups; /* List of all available groups. */
struct list scopes; /* List of all available scopes. */
struct flt_ot_counters cnt; /* Various counters related to filter operation. */
};
#define flt_ot_conf_ph_group_free flt_ot_conf_ph_free
#define flt_ot_conf_ph_scope_free flt_ot_conf_ph_free
struct flt_ot_conf_ph *flt_ot_conf_ph_init(const char *id, int linenum, struct list *head, char **err);
void flt_ot_conf_ph_free(struct flt_ot_conf_ph **ptr);
struct flt_ot_conf_sample_expr *flt_ot_conf_sample_expr_init(const char *id, int linenum, struct list *head, char **err);
void flt_ot_conf_sample_expr_free(struct flt_ot_conf_sample_expr **ptr);
struct flt_ot_conf_sample *flt_ot_conf_sample_init(char **args, int linenum, struct list *head, char **err);
void flt_ot_conf_sample_free(struct flt_ot_conf_sample **ptr);
struct flt_ot_conf_str *flt_ot_conf_str_init(const char *id, int linenum, struct list *head, char **err);
void flt_ot_conf_str_free(struct flt_ot_conf_str **ptr);
struct flt_ot_conf_context *flt_ot_conf_context_init(const char *id, int linenum, struct list *head, char **err);
void flt_ot_conf_context_free(struct flt_ot_conf_context **ptr);
struct flt_ot_conf_span *flt_ot_conf_span_init(const char *id, int linenum, struct list *head, char **err);
void flt_ot_conf_span_free(struct flt_ot_conf_span **ptr);
struct flt_ot_conf_scope *flt_ot_conf_scope_init(const char *id, int linenum, struct list *head, char **err);
void flt_ot_conf_scope_free(struct flt_ot_conf_scope **ptr);
struct flt_ot_conf_group *flt_ot_conf_group_init(const char *id, int linenum, struct list *head, char **err);
void flt_ot_conf_group_free(struct flt_ot_conf_group **ptr);
struct flt_ot_conf_tracer *flt_ot_conf_tracer_init(const char *id, int linenum, char **err);
void flt_ot_conf_tracer_free(struct flt_ot_conf_tracer **ptr);
struct flt_ot_conf *flt_ot_conf_init(struct proxy *px);
void flt_ot_conf_free(struct flt_ot_conf **ptr);
#endif /* _OPENTRACING_CONF_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,46 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_CONFIG_H_
#define _OPENTRACING_CONFIG_H_
#undef DEBUG_OT_SYSTIME
#define USE_POOL_BUFFER
#define USE_POOL_OT_SPAN_CONTEXT
#define USE_POOL_OT_SCOPE_SPAN
#define USE_POOL_OT_SCOPE_CONTEXT
#define USE_POOL_OT_RUNTIME_CONTEXT
#define USE_TRASH_CHUNK
#define FLT_OT_ID_MAXLEN 64
#define FLT_OT_MAXTAGS 8
#define FLT_OT_MAXBAGGAGES 8
#define FLT_OT_RATE_LIMIT_MAX 100.0
#define FLT_OT_DEBUG_LEVEL 0b00001111
#endif /* _OPENTRACING_CONFIG_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,104 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_DEBUG_H_
#define _OPENTRACING_DEBUG_H_
#ifdef DEBUG_FULL
# define DEBUG_OT
#endif
#ifdef DEBUG_OT
# ifdef DEBUG_OT_SYSTIME
# define FLT_OT_DBG_FMT(f) "[% 2d] %ld.%06ld [" FLT_OT_SCOPE "]: " f, tid, date.tv_sec, date.tv_usec
# else
# define FLT_OT_DBG_FMT(f) "[% 2d] %11.6f [" FLT_OT_SCOPE "]: " f, tid, FLT_OT_TV_UDIFF(&(flt_ot_debug.start), &date) / 1e6
# endif
# define FLT_OT_DBG_INDENT " "
# define FLT_OT_DBG(l,f, ...) \
do { \
if (!(l) || (flt_ot_debug.level & (1 << (l)))) \
(void)fprintf(stderr, FLT_OT_DBG_FMT("%.*s" f "\n"), \
flt_ot_dbg_indent_level, FLT_OT_DBG_INDENT, ##__VA_ARGS__); \
} while (0)
# define FLT_OT_FUNC(f, ...) do { FLT_OT_DBG(1, "%s(" f ") {", __func__, ##__VA_ARGS__); flt_ot_dbg_indent_level += 3; } while (0)
# define FLT_OT_RETURN(a) do { flt_ot_dbg_indent_level -= 3; FLT_OT_DBG(1, "}"); return a; } while (0)
# define FLT_OT_RETURN_EX(a,t,f) do { flt_ot_dbg_indent_level -= 3; { t _r = (a); FLT_OT_DBG(1, "} = " f, _r); return _r; } } while (0)
# define FLT_OT_RETURN_INT(a) FLT_OT_RETURN_EX((a), int, "%d")
# define FLT_OT_RETURN_PTR(a) FLT_OT_RETURN_EX((a), void *, "%p")
# define FLT_OT_DBG_IFDEF(a,b) a
# define FLT_OT_DBG_ARGS(a, ...) a, ##__VA_ARGS__
# define FLT_OT_DBG_BUF(a,b) do { FLT_OT_DBG((a), "%p:{ %zu %p %zu %zu }", (b), (b)->size, (b)->area, (b)->data, (b)->head); } while (0)
struct flt_ot_debug {
#ifndef DEBUG_OT_SYSTIME
struct timeval start;
#endif
uint8_t level;
};
extern THREAD_LOCAL int flt_ot_dbg_indent_level;
extern struct flt_ot_debug flt_ot_debug;
#else
# define FLT_OT_DBG(...) while (0)
# define FLT_OT_FUNC(...) while (0)
# define FLT_OT_RETURN(a) return a
# define FLT_OT_RETURN_EX(a,t,f) return a
# define FLT_OT_RETURN_INT(a) return a
# define FLT_OT_RETURN_PTR(a) return a
# define FLT_OT_DBG_IFDEF(a,b) b
# define FLT_OT_DBG_ARGS(...)
# define FLT_OT_DBG_BUF(a,b) while (0)
#endif /* DEBUG_OT */
/*
* ON | NOLOGNORM |
* -----+-----------+-------------
* 0 | 0 | no log
* 0 | 1 | no log
* 1 | 0 | log all
* 1 | 1 | log errors
* -----+-----------+-------------
*/
#define FLT_OT_LOG(l,f, ...) \
do { \
if (!(conf->tracer->logging & FLT_OT_LOGGING_ON)) \
FLT_OT_DBG(3, "NOLOG[%d]: [" FLT_OT_SCOPE "]: [%s] " f, (l), conf->id, ##__VA_ARGS__); \
else if ((conf->tracer->logging & FLT_OT_LOGGING_NOLOGNORM) && ((l) > LOG_ERR)) \
FLT_OT_DBG(2, "NOLOG[%d]: [" FLT_OT_SCOPE "]: [%s] " f, (l), conf->id, ##__VA_ARGS__); \
else { \
send_log(&(conf->tracer->proxy_log), (l), "[" FLT_OT_SCOPE "]: [%s] " f "\n", conf->id, ##__VA_ARGS__); \
\
FLT_OT_DBG(1, "LOG[%d]: %s", (l), logline); \
} \
} while (0)
#endif /* _OPENTRACING_DEBUG_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,107 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_DEFINE_H_
#define _OPENTRACING_DEFINE_H_
#define FLT_OT_DEREF(a,m,v) (((a) != NULL) ? (a)->m : (v))
#define FLT_OT_DDEREF(a,m,v) ((((a) != NULL) && (*(a) != NULL)) ? (*(a))->m : (v))
#define FLT_OT_TABLESIZE(a) (sizeof(a) / sizeof((a)[0]))
#define FLT_OT_IN_RANGE(v,a,b) (((v) >= (a)) && ((v) <= (b)))
#define FLT_OT_DPTR_ARGS(a) (a), ((a) == NULL) ? NULL : *(a)
#define FLT_OT_ARG_ISVALID(n) ((args[n] != NULL) && *args[n])
#define FLT_OT_TV_UDIFF(a,b) (((b)->tv_sec - (a)->tv_sec) * 1000000 + (b)->tv_usec - (a)->tv_usec)
#define FLT_OT_U32_FLOAT(a,b) ((a) * (double)(b) / UINT32_MAX)
#define FLT_OT_FLOAT_U32(a,b) ((uint32_t)((a) / (double)(b) * UINT32_MAX + 0.5))
#define FLT_OT_STR_DASH_72 "------------------------------------------------------------------------"
#define FLT_OT_STR_DASH_78 FLT_OT_STR_DASH_72 "------"
#define FLT_OT_STR_FLAG_YN(a) (a) ? "yes" : "no"
#define FLT_OT_STR_SIZE(a) (sizeof(a) - 1)
#define FLT_OT_STR_ADDRSIZE(a) (a), FLT_OT_STR_SIZE(a)
#define FLT_OT_STR_ISVALID(a) (((a) != NULL) && (*(a) != '\0'))
#define FLT_OT_STR_CMP(S,s,l) (((l) == FLT_OT_STR_SIZE(S)) && (memcmp((s), FLT_OT_STR_ADDRSIZE(S)) == 0))
#define FLT_OT_STR_ELLIPSIS(a,n) do { if ((a) != NULL) { if ((n) > 0) (a)[(n) - 1] = '\0'; if ((n) > 3) (a)[(n) - 2] = (a)[(n) - 3] = (a)[(n) - 4] = '.'; } } while (0)
#define FLT_OT_NIBBLE_TO_HEX(a) ((a) + (((a) < 10) ? '0' : ('a' - 10)))
#define FLT_OT_FREE(a) do { if ((a) != NULL) OTC_DBG_FREE(a); } while (0)
#define FLT_OT_FREE_VOID(a) do { if ((a) != NULL) OTC_DBG_FREE((void *)(a)); } while (0)
#define FLT_OT_FREE_CLEAR(a) do { if ((a) != NULL) { OTC_DBG_FREE(a); (a) = NULL; } } while (0)
#define FLT_OT_STRDUP(s) OTC_DBG_STRDUP(s)
#define FLT_OT_STRNDUP(s,n) OTC_DBG_STRNDUP((s), (n))
#define FLT_OT_CALLOC(n,e) OTC_DBG_CALLOC((n), (e))
#define FLT_OT_MALLOC(s) OTC_DBG_MALLOC((s))
#define FLT_OT_MEMINFO() OTC_DBG_MEMINFO()
#define FLT_OT_RUN_ONCE(f) do { static bool __f = 1; if (__f) { __f = 0; f; } } while (0)
#define FLT_OT_LIST_ISVALID(a) (((a) != NULL) && ((a)->n != NULL) && ((a)->p != NULL))
#define FLT_OT_LIST_DEL(a) do { if (FLT_OT_LIST_ISVALID(a)) LIST_DELETE(a); } while (0)
#define FLT_OT_LIST_DESTROY(t,h) \
do { \
struct flt_ot_conf_##t *_ptr, *_back; \
\
if (!FLT_OT_LIST_ISVALID(h) || LIST_ISEMPTY(h)) \
break; \
\
FLT_OT_DBG(2, "- deleting " #t " list %s", flt_ot_list_debug(h)); \
\
list_for_each_entry_safe(_ptr, _back, (h), list) \
flt_ot_conf_##t##_free(&_ptr); \
} while (0)
#define FLT_OT_BUFFER_THR(b,m,n,p) \
static THREAD_LOCAL char b[m][n]; \
static THREAD_LOCAL size_t __idx = 0; \
char *p = b[__idx]; \
__idx = (__idx + 1) % (m)
#define FLT_OT_ERR(f, ...) \
do { \
if ((err != NULL) && (*err == NULL)) { \
(void)memprintf(err, f, ##__VA_ARGS__); \
\
FLT_OT_DBG(3, "%d err: '%s'", __LINE__, *err); \
} \
} while (0)
#define FLT_OT_ERR_APPEND(f, ...) \
do { \
if (err != NULL) \
(void)memprintf(err, f, ##__VA_ARGS__); \
} while (0)
#define FLT_OT_ERR_FREE(p) \
do { \
if ((p) == NULL) \
break; \
\
FLT_OT_DBG(0, "%s:%d: ERROR: %s", __func__, __LINE__, (p)); \
FLT_OT_FREE_CLEAR(p); \
} while (0)
#endif /* _OPENTRACING_DEFINE_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,120 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_EVENT_H_
#define _OPENTRACING_EVENT_H_
/*
* This must be defined in order for macro FLT_OT_EVENT_DEFINES
* and structure flt_ot_event_data to have the correct contents.
*/
#define AN_REQ_NONE 0
#define AN_REQ_CLIENT_SESS_START 0
#define AN_REQ_SERVER_UNAVAILABLE 0
#define AN_REQ_CLIENT_SESS_END 0
#define AN_RES_SERVER_SESS_START 0
#define AN_RES_SERVER_SESS_END 0
#define SMP_VAL_FE_ 0
#define SMP_VAL_BE_ 0
/*
* Event names are selected to be somewhat compatible with the SPOE filter,
* from which the following names are taken:
* - on-client-session -> on-client-session-start
* - on-frontend-tcp-request
* - on-frontend-http-request
* - on-backend-tcp-request
* - on-backend-http-request
* - on-server-session -> on-server-session-start
* - on-tcp-response
* - on-http-response
*
* FLT_OT_EVENT_NONE is used as an index for 'ot-scope' sections that do not
* have an event defined. The 'ot-scope' sections thus defined can be used
* within the 'ot-group' section.
*
* A description of the macro arguments can be found in the structure
* flt_ot_event_data definition
*/
#define FLT_OT_EVENT_DEFINES \
FLT_OT_EVENT_DEF( NONE, REQ, , , 0, "") \
FLT_OT_EVENT_DEF( CLIENT_SESS_START, REQ, CON_ACC, , 1, "on-client-session-start") \
FLT_OT_EVENT_DEF( INSPECT_FE, REQ, REQ_CNT, , 1, "on-frontend-tcp-request") \
FLT_OT_EVENT_DEF( WAIT_HTTP, REQ, , , 1, "on-http-wait-request") \
FLT_OT_EVENT_DEF( HTTP_BODY, REQ, , , 1, "on-http-body-request") \
FLT_OT_EVENT_DEF( HTTP_PROCESS_FE, REQ, HRQ_HDR, , 1, "on-frontend-http-request") \
FLT_OT_EVENT_DEF( SWITCHING_RULES, REQ, , , 1, "on-switching-rules-request") \
FLT_OT_EVENT_DEF( INSPECT_BE, REQ, REQ_CNT, REQ_CNT, 1, "on-backend-tcp-request") \
FLT_OT_EVENT_DEF( HTTP_PROCESS_BE, REQ, HRQ_HDR, HRQ_HDR, 1, "on-backend-http-request") \
/* FLT_OT_EVENT_DEF( HTTP_TARPIT, REQ, , , 1, "on-http-tarpit-request") */ \
FLT_OT_EVENT_DEF( SRV_RULES, REQ, , , 1, "on-process-server-rules-request") \
FLT_OT_EVENT_DEF( HTTP_INNER, REQ, , , 1, "on-http-process-request") \
FLT_OT_EVENT_DEF( PRST_RDP_COOKIE, REQ, , , 1, "on-tcp-rdp-cookie-request") \
FLT_OT_EVENT_DEF( STICKING_RULES, REQ, , , 1, "on-process-sticking-rules-request") \
FLT_OT_EVENT_DEF( CLIENT_SESS_END, REQ, , , 0, "on-client-session-end") \
FLT_OT_EVENT_DEF(SERVER_UNAVAILABLE, REQ, , , 0, "on-server-unavailable") \
\
FLT_OT_EVENT_DEF( SERVER_SESS_START, RES, , SRV_CON, 0, "on-server-session-start") \
FLT_OT_EVENT_DEF( INSPECT, RES, RES_CNT, RES_CNT, 0, "on-tcp-response") \
FLT_OT_EVENT_DEF( WAIT_HTTP, RES, , , 1, "on-http-wait-response") \
FLT_OT_EVENT_DEF( STORE_RULES, RES, , , 1, "on-process-store-rules-response") \
FLT_OT_EVENT_DEF( HTTP_PROCESS_BE, RES, HRS_HDR, HRS_HDR, 1, "on-http-response") \
FLT_OT_EVENT_DEF( SERVER_SESS_END, RES, , , 0, "on-server-session-end")
enum FLT_OT_EVENT_enum {
#define FLT_OT_EVENT_DEF(a,b,c,d,e,f) FLT_OT_EVENT_##b##_##a,
FLT_OT_EVENT_DEFINES
FLT_OT_EVENT_MAX
#undef FLT_OT_EVENT_DEF
};
enum FLT_OT_EVENT_SAMPLE_enum {
FLT_OT_EVENT_SAMPLE_TAG = 0,
FLT_OT_EVENT_SAMPLE_LOG,
FLT_OT_EVENT_SAMPLE_BAGGAGE,
};
struct flt_ot_event_data {
uint an_bit; /* Used channel analyser. */
uint smp_opt_dir; /* Fetch direction (request/response). */
uint smp_val_fe; /* Valid FE fetch location. */
uint smp_val_be; /* Valid BE fetch location. */
bool flag_http_inject; /* Span context injection allowed. */
const char *name; /* Filter event name. */
};
struct flt_ot_conf_scope;
extern const struct flt_ot_event_data flt_ot_event_data[FLT_OT_EVENT_MAX];
int flt_ot_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_ot_conf_scope *conf_scope, const struct timespec *ts, uint dir, char **err);
int flt_ot_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err);
#endif /* _OPENTRACING_EVENT_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,68 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_FILTER_H_
#define _OPENTRACING_FILTER_H_
#define FLT_OT_FMT_NAME "'" FLT_OT_OPT_NAME "' : "
#define FLT_OT_FMT_TYPE "'filter' : "
#define FLT_OT_VAR_UUID "sess", "ot", "uuid"
#define FLT_OT_ALERT(f, ...) ha_alert(FLT_OT_FMT_TYPE FLT_OT_FMT_NAME f "\n", ##__VA_ARGS__)
#define FLT_OT_CONDITION_IF "if"
#define FLT_OT_CONDITION_UNLESS "unless"
enum FLT_OT_RET_enum {
FLT_OT_RET_ERROR = -1,
FLT_OT_RET_WAIT = 0,
FLT_OT_RET_IGNORE = 0,
FLT_OT_RET_OK = 1,
};
#define FLT_OT_DBG_LIST(d,m,p,t,v,f) \
do { \
if (LIST_ISEMPTY(&((d)->m##s))) { \
FLT_OT_DBG(3, p "- no " #m "s " t); \
} else { \
const struct flt_ot_conf_##m *v; \
\
FLT_OT_DBG(3, p "- " t " " #m "s: %s", \
flt_ot_list_debug(&((d)->m##s))); \
list_for_each_entry(v, &((d)->m##s), list) \
do { f; } while (0); \
} \
} while (0)
extern const char *ot_flt_id;
extern struct flt_ops flt_ot_ops;
bool flt_ot_is_disabled(const struct filter *f FLT_OT_DBG_ARGS(, int event));
#endif /* _OPENTRACING_FILTER_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,61 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_GROUP_H_
#define _OPENTRACING_GROUP_H_
#define FLT_OT_ACTION_GROUP "ot-group"
enum FLT_OT_ARG_enum {
FLT_OT_ARG_FILTER_ID = 0,
FLT_OT_ARG_GROUP_ID,
FLT_OT_ARG_FLT_CONF = 0,
FLT_OT_ARG_CONF,
FLT_OT_ARG_GROUP,
};
/*
* A description of the macro arguments can be found in the structure
* flt_ot_group_data definition
*/
#define FLT_OT_GROUP_DEFINES \
FLT_OT_GROUP_DEF(ACT_F_TCP_REQ_CON, SMP_VAL_FE_CON_ACC, SMP_OPT_DIR_REQ) \
FLT_OT_GROUP_DEF(ACT_F_TCP_REQ_SES, SMP_VAL_FE_SES_ACC, SMP_OPT_DIR_REQ) \
FLT_OT_GROUP_DEF(ACT_F_TCP_REQ_CNT, SMP_VAL_FE_REQ_CNT, SMP_OPT_DIR_REQ) \
FLT_OT_GROUP_DEF(ACT_F_TCP_RES_CNT, SMP_VAL_BE_RES_CNT, SMP_OPT_DIR_RES) \
FLT_OT_GROUP_DEF(ACT_F_HTTP_REQ, SMP_VAL_FE_HRQ_HDR, SMP_OPT_DIR_REQ) \
FLT_OT_GROUP_DEF(ACT_F_HTTP_RES, SMP_VAL_BE_HRS_HDR, SMP_OPT_DIR_RES)
struct flt_ot_group_data {
enum act_from act_from; /* ACT_F_* */
uint smp_val; /* Valid FE/BE fetch location. */
uint smp_opt_dir; /* Fetch direction (request/response). */
};
#endif /* _OPENTRACING_GROUP_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,41 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_HTTP_H_
#define _OPENTRACING_HTTP_H_
#ifndef DEBUG_OT
# define flt_ot_http_headers_dump(...) while (0)
#else
void flt_ot_http_headers_dump(const struct channel *chn);
#endif
struct otc_text_map *flt_ot_http_headers_get(struct channel *chn, const char *prefix, size_t len, char **err);
int flt_ot_http_header_set(struct channel *chn, const char *prefix, const char *name, const char *value, char **err);
int flt_ot_http_headers_remove(struct channel *chn, const char *prefix, char **err);
#endif /* _OPENTRACING_HTTP_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,66 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_INCLUDE_H_
#define _OPENTRACING_INCLUDE_H_
#include <errno.h>
#include <stdbool.h>
#include <haproxy/api.h>
#include <haproxy/cfgparse.h>
#include <haproxy/acl.h>
#include <haproxy/cli.h>
#include <haproxy/clock.h>
#include <haproxy/filters.h>
#include <haproxy/http_htx.h>
#include <haproxy/http_rules.h>
#include <haproxy/log.h>
#include <haproxy/proxy.h>
#include <haproxy/sample.h>
#include <haproxy/tcp_rules.h>
#include <haproxy/tools.h>
#include <haproxy/vars.h>
#include "config.h"
#include "debug.h"
#include "define.h"
#include "cli.h"
#include "event.h"
#include "conf.h"
#include "filter.h"
#include "group.h"
#include "http.h"
#include "opentracing.h"
#include "parser.h"
#include "pool.h"
#include "scope.h"
#include "util.h"
#include "vars.h"
#endif /* _OPENTRACING_INCLUDE_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,86 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_OT_H_
#define _OPENTRACING_OT_H_
#include <opentracing-c-wrapper/include.h>
#define FLT_OT_VSET(p,t,v) \
do { (p)->type = otc_value_##t; (p)->value.t##_value = (v); } while (0)
#define FLT_OT_DBG_TEXT_MAP(a) \
FLT_OT_DBG(3, "%p:{ %p %p %zu/%zu %hhu }", \
(a), (a)->key, (a)->value, (a)->count, (a)->size, (a)->is_dynamic)
#define FLT_OT_DBG_TEXT_CARRIER(a,f) \
FLT_OT_DBG(3, "%p:{ { %p %p %zu/%zu %hhu } %p }", \
(a), (a)->text_map.key, (a)->text_map.value, (a)->text_map.count, \
(a)->text_map.size, (a)->text_map.is_dynamic, (a)->f)
#define FLT_OT_DBG_CUSTOM_CARRIER(a,f) \
FLT_OT_DBG(3, "%p:{ { %p %zu %hhu } %p }", \
(a), (a)->binary_data.data, (a)->binary_data.size, \
(a)->binary_data.is_dynamic, (a)->f)
#define FLT_OT_DBG_SPAN_CONTEXT(a) \
FLT_OT_DBG(3, "%p:{ %" PRId64 " %p %p }", (a), (a)->idx, (a)->span, (a)->destroy)
#ifndef DEBUG_OT
# define ot_debug() while (0)
# define ot_text_map_show(...) while (0)
#else
void ot_text_map_show(const struct otc_text_map *text_map);
void ot_debug(void);
#endif
int ot_init(struct otc_tracer **tracer, const char *plugin, char **err);
int ot_start(struct otc_tracer *tracer, const char *cfgbuf, char **err);
struct otc_span *ot_span_init(struct otc_tracer *tracer, const char *operation_name, const struct timespec *ts_steady, const struct timespec *ts_system, int ref_type, int ref_ctx_idx, const struct otc_span *ref_span, const struct otc_tag *tags, int num_tags, char **err);
int ot_span_tag(struct otc_span *span, const struct otc_tag *tags, int num_tags);
int ot_span_log(struct otc_span *span, const struct otc_log_field *log_fields, int num_fields);
int ot_span_set_baggage(struct otc_span *span, const struct otc_text_map *baggage);
struct otc_span_context *ot_inject_http_headers(struct otc_tracer *tracer, const struct otc_span *span, struct otc_http_headers_writer *carrier, char **err);
struct otc_span_context *ot_extract_http_headers(struct otc_tracer *tracer, struct otc_http_headers_reader *carrier, const struct otc_text_map *text_map, char **err);
void ot_span_finish(struct otc_span **span, const struct timespec *ts_finish, const struct timespec *log_ts, const char *log_key, const char *log_value, ...);
void ot_close(struct otc_tracer **tracer);
/* Unused code. */
struct otc_span *ot_span_init_va(struct otc_tracer *tracer, const char *operation_name, const struct timespec *ts_steady, const struct timespec *ts_system, int ref_type, int ref_ctx_idx, const struct otc_span *ref_span, char **err, const char *tag_key, const char *tag_value, ...);
int ot_span_tag_va(struct otc_span *span, const char *key, int type, ...);
int ot_span_log_va(struct otc_span *span, const char *key, const char *value, ...);
int ot_span_log_fmt(struct otc_span *span, const char *key, const char *format, ...) __attribute__ ((format(printf, 3, 4)));
int ot_span_set_baggage_va(struct otc_span *span, const char *key, const char *value, ...);
struct otc_text_map *ot_span_baggage_va(const struct otc_span *span, const char *key, ...);
struct otc_span_context *ot_inject_text_map(struct otc_tracer *tracer, const struct otc_span *span, struct otc_text_map_writer *carrier);
struct otc_span_context *ot_inject_binary(struct otc_tracer *tracer, const struct otc_span *span, struct otc_custom_carrier_writer *carrier);
struct otc_span_context *ot_extract_text_map(struct otc_tracer *tracer, struct otc_text_map_reader *carrier, const struct otc_text_map *text_map);
struct otc_span_context *ot_extract_binary(struct otc_tracer *tracer, struct otc_custom_carrier_reader *carrier, const struct otc_binary_data *binary_data);
#endif /* _OPENTRACING_OT_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,172 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_PARSER_H_
#define _OPENTRACING_PARSER_H_
#define FLT_OT_SCOPE "OT"
/*
* filter FLT_OT_OPT_NAME FLT_OT_OPT_FILTER_ID <FLT_OT_OPT_FILTER_ID_DEFAULT> FLT_OT_OPT_CONFIG <file>
*/
#define FLT_OT_OPT_NAME "opentracing"
#define FLT_OT_OPT_FILTER_ID "id"
#define FLT_OT_OPT_FILTER_ID_DEFAULT "ot-filter"
#define FLT_OT_OPT_CONFIG "config"
#define FLT_OT_PARSE_SECTION_TRACER_ID "ot-tracer"
#define FLT_OT_PARSE_SECTION_GROUP_ID "ot-group"
#define FLT_OT_PARSE_SECTION_SCOPE_ID "ot-scope"
#define FLT_OT_PARSE_SPAN_ROOT "root"
#define FLT_OT_PARSE_SPAN_REF_CHILD "child-of"
#define FLT_OT_PARSE_SPAN_REF_FOLLOWS "follows-from"
#define FLT_OT_PARSE_CTX_AUTONAME "-"
#define FLT_OT_PARSE_CTX_IGNORE_NAME '-'
#define FLT_OT_PARSE_CTX_USE_HEADERS "use-headers"
#define FLT_OT_PARSE_CTX_USE_VARS "use-vars"
#define FLT_OT_PARSE_OPTION_HARDERR "hard-errors"
#define FLT_OT_PARSE_OPTION_DISABLED "disabled"
#define FLT_OT_PARSE_OPTION_NOLOGNORM "dontlog-normal"
/*
* A description of the macro arguments can be found in the structure
* flt_ot_parse_data definition
*/
#define FLT_OT_PARSE_TRACER_DEFINES \
FLT_OT_PARSE_TRACER_DEF( ID, 0, CHAR, 2, 2, "ot-tracer", " <name>") \
FLT_OT_PARSE_TRACER_DEF( ACL, 0, CHAR, 3, 0, "acl", " <name> <criterion> [flags] [operator] <value> ...") \
FLT_OT_PARSE_TRACER_DEF( LOG, 0, CHAR, 2, 0, "log", " { global | <addr> [len <len>] [format <fmt>] <facility> [<level> [<minlevel>]] }") \
FLT_OT_PARSE_TRACER_DEF( CONFIG, 0, NONE, 2, 2, "config", " <file>") \
FLT_OT_PARSE_TRACER_DEF( PLUGIN, 0, NONE, 2, 2, "plugin", " <file>") \
FLT_OT_PARSE_TRACER_DEF( GROUPS, 0, NONE, 2, 0, "groups", " <name> ...") \
FLT_OT_PARSE_TRACER_DEF( SCOPES, 0, NONE, 2, 0, "scopes", " <name> ...") \
FLT_OT_PARSE_TRACER_DEF( RATE_LIMIT, 0, NONE, 2, 2, "rate-limit", " <value>") \
FLT_OT_PARSE_TRACER_DEF( OPTION, 0, NONE, 2, 2, "option", " { disabled | dontlog-normal | hard-errors }") \
FLT_OT_PARSE_TRACER_DEF(DEBUG_LEVEL, 0, NONE, 2, 2, "debug-level", " <value>")
#define FLT_OT_PARSE_GROUP_DEFINES \
FLT_OT_PARSE_GROUP_DEF( ID, 0, CHAR, 2, 2, "ot-group", " <name>") \
FLT_OT_PARSE_GROUP_DEF(SCOPES, 0, NONE, 2, 0, "scopes", " <name> ...")
#ifdef USE_OT_VARS
# define FLT_OT_PARSE_SCOPE_INJECT_HELP " <name-prefix> [use-vars] [use-headers]"
# define FLT_OT_PARSE_SCOPE_EXTRACT_HELP " <name-prefix> [use-vars | use-headers]"
#else
# define FLT_OT_PARSE_SCOPE_INJECT_HELP " <name-prefix> [use-headers]"
# define FLT_OT_PARSE_SCOPE_EXTRACT_HELP " <name-prefix> [use-headers]"
#endif
/*
* In case the possibility of working with OpenTracing context via HAProxyu
* variables is not used, args_max member of the structure flt_ot_parse_data
* should be reduced for 'inject' keyword. However, this is not critical
* because in this case the 'use-vars' argument cannot be entered anyway,
* so I will not complicate it here with additional definitions.
*/
#define FLT_OT_PARSE_SCOPE_DEFINES \
FLT_OT_PARSE_SCOPE_DEF( ID, 0, CHAR, 2, 2, "ot-scope", " <name>") \
FLT_OT_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 5, "span", " <name> [<reference>] [root]") \
FLT_OT_PARSE_SCOPE_DEF( TAG, 1, NONE, 3, 0, "tag", " <name> <sample> ...") \
FLT_OT_PARSE_SCOPE_DEF( LOG, 1, NONE, 3, 0, "log", " <name> <sample> ...") \
FLT_OT_PARSE_SCOPE_DEF(BAGGAGE, 1, VAR, 3, 0, "baggage", " <name> <sample> ...") \
FLT_OT_PARSE_SCOPE_DEF( INJECT, 1, CTX, 2, 4, "inject", FLT_OT_PARSE_SCOPE_INJECT_HELP) \
FLT_OT_PARSE_SCOPE_DEF(EXTRACT, 0, CTX, 2, 3, "extract", FLT_OT_PARSE_SCOPE_EXTRACT_HELP) \
FLT_OT_PARSE_SCOPE_DEF( FINISH, 0, NONE, 2, 0, "finish", " <name> ...") \
FLT_OT_PARSE_SCOPE_DEF( ACL, 0, CHAR, 3, 0, "acl", " <name> <criterion> [flags] [operator] <value> ...") \
FLT_OT_PARSE_SCOPE_DEF( EVENT, 0, NONE, 2, 0, "event", " <name> [{ if | unless } <condition>]")
enum FLT_OT_PARSE_INVCHAR_enum {
FLT_OT_PARSE_INVALID_NONE,
FLT_OT_PARSE_INVALID_CHAR,
FLT_OT_PARSE_INVALID_DOM,
FLT_OT_PARSE_INVALID_CTX,
FLT_OT_PARSE_INVALID_VAR,
};
enum FLT_OT_PARSE_TRACER_enum {
#define FLT_OT_PARSE_TRACER_DEF(a,b,c,d,e,f,g) FLT_OT_PARSE_TRACER_##a,
FLT_OT_PARSE_TRACER_DEFINES
#undef FLT_OT_PARSE_TRACER_DEF
};
enum FLT_OT_PARSE_GROUP_enum {
#define FLT_OT_PARSE_GROUP_DEF(a,b,c,d,e,f,g) FLT_OT_PARSE_GROUP_##a,
FLT_OT_PARSE_GROUP_DEFINES
#undef FLT_OT_PARSE_GROUP_DEF
};
enum FLT_OT_PARSE_SCOPE_enum {
#define FLT_OT_PARSE_SCOPE_DEF(a,b,c,d,e,f,g) FLT_OT_PARSE_SCOPE_##a,
FLT_OT_PARSE_SCOPE_DEFINES
#undef FLT_OT_PARSE_SCOPE_DEF
};
enum FLT_OT_CTX_USE_enum {
FLT_OT_CTX_USE_VARS = 1 << 0,
FLT_OT_CTX_USE_HEADERS = 1 << 1,
};
struct flt_ot_parse_data {
int keyword; /* Keyword index. */
bool flag_check_id; /* Whether the group ID must be defined for the keyword. */
int check_name; /* Checking allowed characters in the name. */
int args_min; /* The minimum number of arguments required. */
int args_max; /* The maximum number of arguments allowed. */
const char *name; /* Keyword name. */
const char *usage; /* Usage text to be printed in case of an error. */
};
#define FLT_OT_PARSE_WARNING(f, ...) \
ha_warning("parsing [%s:%d] : " FLT_OT_FMT_TYPE FLT_OT_FMT_NAME "'" f "'\n", ##__VA_ARGS__);
#define FLT_OT_PARSE_ALERT(f, ...) \
do { \
ha_alert("parsing [%s:%d] : " FLT_OT_FMT_TYPE FLT_OT_FMT_NAME "'" f "'\n", ##__VA_ARGS__); \
\
retval |= ERR_ABORT | ERR_ALERT; \
} while (0)
#define FLT_OT_POST_PARSE_ALERT(f, ...) \
FLT_OT_PARSE_ALERT(f, flt_ot_current_config->cfg_file, ##__VA_ARGS__)
#define FLT_OT_PARSE_ERR(e,f, ...) \
do { \
if (*(e) == NULL) \
(void)memprintf((e), f, ##__VA_ARGS__); \
\
retval |= ERR_ABORT | ERR_ALERT; \
} while (0)
#define FLT_OT_PARSE_IFERR_ALERT() \
do { \
if (err == NULL) \
break; \
\
FLT_OT_PARSE_ALERT("%s", file, linenum, err); \
FLT_OT_ERR_FREE(err); \
} while (0)
#endif /* _OPENTRACING_PARSER_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,39 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_POOL_H_
#define _OPENTRACING_POOL_H_
void *flt_ot_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err);
void *flt_ot_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err);
void flt_ot_pool_free(struct pool_head *pool, void **ptr);
struct buffer *flt_ot_trash_alloc(bool flag_clear, char **err);
void flt_ot_trash_free(struct buffer **ptr);
#endif /* _OPENTRACING_POOL_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,126 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_SCOPE_H_
#define _OPENTRACING_SCOPE_H_
#define FLT_OT_SCOPE_SPAN_FINISH_REQ "*req*"
#define FLT_OT_SCOPE_SPAN_FINISH_RES "*res*"
#define FLT_OT_SCOPE_SPAN_FINISH_ALL "*"
#define FLT_OT_RT_CTX(a) ((struct flt_ot_runtime_context *)(a))
#define FLT_OT_DBG_SCOPE_SPAN(f,a) \
FLT_OT_DBG(3, "%s%p:{ '%s' %zu %u %hhu %p %d %p %p }", \
(f), (a), FLT_OT_STR_HDR_ARGS(a, id), (a)->smp_opt_dir, \
(a)->flag_finish, (a)->span, (a)->ref_type, (a)->ref_span, (a)->ref_ctx)
#define FLT_OT_DBG_SCOPE_CONTEXT(f,a) \
FLT_OT_DBG(3, "%s%p:{ '%s' %zu %u %hhu %p }", \
(f), (a), FLT_OT_STR_HDR_ARGS(a, id), (a)->smp_opt_dir, \
(a)->flag_finish, (a)->context)
#define FLT_OT_DBG_SCOPE_DATA(f,a) \
FLT_OT_DBG(3, "%s%p:{ %p %d %p %p %d }", \
(f), (a), (a)->tags, (a)->num_tags, (a)->baggage, (a)->log_fields, (a)->num_log_fields)
#define FLT_OT_DBG_RUNTIME_CONTEXT(f,a) \
FLT_OT_DBG(3, "%s%p:{ %p %p '%s' %hhu %hhu 0x%02hhx 0x%08x %s %s }", \
(f), (a), (a)->stream, (a)->filter, (a)->uuid, (a)->flag_harderr, \
(a)->flag_disabled, (a)->logging, (a)->analyzers, flt_ot_list_debug(&((a)->spans)), \
flt_ot_list_debug(&((a)->contexts)))
#define FLT_OT_CONST_STR_HDR(a) \
struct { \
const char *a; \
size_t a##_len; \
}
struct flt_ot_scope_data {
struct otc_tag tags[FLT_OT_MAXTAGS]; /* Defined tags. */
int num_tags; /* The number of tags used. */
struct otc_text_map *baggage; /* Defined baggage. */
struct otc_log_field log_fields[OTC_MAXLOGFIELDS]; /* Defined logs. */
int num_log_fields; /* The number of log fields used. */
};
/* flt_ot_runtime_context->spans */
struct flt_ot_scope_span {
FLT_OT_CONST_STR_HDR(id); /* The span operation name/len. */
uint smp_opt_dir; /* SMP_OPT_DIR_RE(Q|S) */
bool flag_finish; /* Whether the span is marked for completion. */
struct otc_span *span; /* The current span. */
otc_span_reference_type_t ref_type; /* Span reference type. */
struct otc_span *ref_span; /* Span to which the current span refers. */
struct otc_span_context *ref_ctx; /* Span context to which the current span refers. */
struct list list; /* Used to chain this structure. */
};
/* flt_ot_runtime_context->contexts */
struct flt_ot_scope_context {
FLT_OT_CONST_STR_HDR(id); /* The span context name/len. */
uint smp_opt_dir; /* SMP_OPT_DIR_RE(Q|S) */
bool flag_finish; /* Whether the span context is marked for completion. */
struct otc_span_context *context; /* The current span context. */
struct list list; /* Used to chain this structure. */
};
/* The runtime filter context attached to a stream. */
struct flt_ot_runtime_context {
struct stream *stream; /* The stream to which the filter is attached. */
struct filter *filter; /* The OpenTracing filter. */
char uuid[40]; /* Randomly generated UUID. */
bool flag_harderr; /* [0 1] */
bool flag_disabled; /* [0 1] */
uint8_t logging; /* [0 1 3] */
uint analyzers; /* Executed channel analyzers. */
struct list spans; /* The scope spans. */
struct list contexts; /* The scope contexts. */
};
#ifndef DEBUG_OT
# define flt_ot_pools_info() while (0)
#else
void flt_ot_pools_info(void);
#endif
struct flt_ot_runtime_context *flt_ot_runtime_context_init(struct stream *s, struct filter *f, char **err);
void flt_ot_runtime_context_free(struct filter *f);
struct flt_ot_scope_span *flt_ot_scope_span_init(struct flt_ot_runtime_context *rt_ctx, const char *id, size_t id_len, otc_span_reference_type_t ref_type, const char *ref_id, size_t ref_id_len, uint dir, char **err);
void flt_ot_scope_span_free(struct flt_ot_scope_span **ptr);
struct flt_ot_scope_context *flt_ot_scope_context_init(struct flt_ot_runtime_context *rt_ctx, struct otc_tracer *tracer, const char *id, size_t id_len, const struct otc_text_map *text_map, uint dir, char **err);
void flt_ot_scope_context_free(struct flt_ot_scope_context **ptr);
void flt_ot_scope_data_free(struct flt_ot_scope_data *ptr);
int flt_ot_scope_finish_mark(const struct flt_ot_runtime_context *rt_ctx, const char *id, size_t id_len);
void flt_ot_scope_finish_marked(const struct flt_ot_runtime_context *rt_ctx, const struct timespec *ts_finish);
void flt_ot_scope_free_unused(struct flt_ot_runtime_context *rt_ctx, struct channel *chn);
#endif /* _OPENTRACING_SCOPE_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,109 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_UTIL_H_
#define _OPENTRACING_UTIL_H_
#define HTTP_METH_STR_OPTIONS "OPTIONS"
#define HTTP_METH_STR_GET "GET"
#define HTTP_METH_STR_HEAD "HEAD"
#define HTTP_METH_STR_POST "POST"
#define HTTP_METH_STR_PUT "PUT"
#define HTTP_METH_STR_DELETE "DELETE"
#define HTTP_METH_STR_TRACE "TRACE"
#define HTTP_METH_STR_CONNECT "CONNECT"
/* Defined in include/haproxy/channel-t.h. */
#define FLT_OT_AN_DEFINES \
FLT_OT_AN_DEF(AN_REQ_INSPECT_FE) \
FLT_OT_AN_DEF(AN_REQ_WAIT_HTTP) \
FLT_OT_AN_DEF(AN_REQ_HTTP_BODY) \
FLT_OT_AN_DEF(AN_REQ_HTTP_PROCESS_FE) \
FLT_OT_AN_DEF(AN_REQ_SWITCHING_RULES) \
FLT_OT_AN_DEF(AN_REQ_INSPECT_BE) \
FLT_OT_AN_DEF(AN_REQ_HTTP_PROCESS_BE) \
FLT_OT_AN_DEF(AN_REQ_HTTP_TARPIT) \
FLT_OT_AN_DEF(AN_REQ_SRV_RULES) \
FLT_OT_AN_DEF(AN_REQ_HTTP_INNER) \
FLT_OT_AN_DEF(AN_REQ_PRST_RDP_COOKIE) \
FLT_OT_AN_DEF(AN_REQ_STICKING_RULES) \
FLT_OT_AN_DEF(AN_REQ_HTTP_XFER_BODY) \
FLT_OT_AN_DEF(AN_REQ_WAIT_CLI) \
FLT_OT_AN_DEF(AN_RES_INSPECT) \
FLT_OT_AN_DEF(AN_RES_WAIT_HTTP) \
FLT_OT_AN_DEF(AN_RES_STORE_RULES) \
FLT_OT_AN_DEF(AN_RES_HTTP_PROCESS_BE) \
FLT_OT_AN_DEF(AN_RES_HTTP_PROCESS_FE) \
FLT_OT_AN_DEF(AN_RES_HTTP_XFER_BODY) \
FLT_OT_AN_DEF(AN_RES_WAIT_CLI)
#define FLT_OT_PROXIES_LIST_START() \
do { \
struct flt_conf *fconf; \
struct proxy *px; \
\
for (px = proxies_list; px != NULL; px = px->next) \
list_for_each_entry(fconf, &(px->filter_configs), list) \
if (fconf->id == ot_flt_id) { \
struct flt_ot_conf *conf = fconf->conf;
#define FLT_OT_PROXIES_LIST_END() \
} \
} while (0)
#ifdef DEBUG_OT
# define FLT_OT_ARGS_DUMP() do { if (flt_ot_debug.level & (1 << 2)) flt_ot_args_dump(args); } while (0)
#else
# define FLT_OT_ARGS_DUMP() while (0)
#endif
#ifndef DEBUG_OT
# define flt_ot_filters_dump() while (0)
#else
void flt_ot_args_dump(char **args);
void flt_ot_filters_dump(void);
const char *flt_ot_chn_label(const struct channel *chn);
const char *flt_ot_pr_mode(const struct stream *s);
const char *flt_ot_stream_pos(const struct stream *s);
const char *flt_ot_type(const struct filter *f);
const char *flt_ot_analyzer(uint an_bit);
const char *flt_ot_str_hex(const void *data, size_t size);
const char *flt_ot_str_ctrl(const void *data, size_t size);
const char *flt_ot_list_debug(const struct list *head);
#endif
ssize_t flt_ot_chunk_add(struct buffer *chk, const void *src, size_t n, char **err);
int flt_ot_args_count(char **args);
void flt_ot_args_to_str(char **args, int idx, char **str);
double flt_ot_strtod(const char *nptr, double limit_min, double limit_max, char **err);
int64_t flt_ot_strtoll(const char *nptr, int64_t limit_min, int64_t limit_max, char **err);
int flt_ot_sample_to_str(const struct sample_data *data, char *value, size_t size, char **err);
int flt_ot_sample_to_value(const char *key, const struct sample_data *data, struct otc_value *value, char **err);
int flt_ot_sample_add(struct stream *s, uint dir, struct flt_ot_conf_sample *sample, struct flt_ot_scope_data *data, int type, char **err);
#endif /* _OPENTRACING_UTIL_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,55 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _OPENTRACING_VARS_H_
#define _OPENTRACING_VARS_H_
#define FLT_OT_VARS_SCOPE "txn"
#define FLT_OT_VAR_CTX_SIZE int8_t
#define FLT_OT_VAR_CHAR_DASH 'D'
#define FLT_OT_VAR_CHAR_SPACE 'S'
struct flt_ot_ctx {
char value[BUFSIZ];
int value_len;
};
typedef int (*flt_ot_ctx_loop_cb)(struct sample *, size_t, const char *, const char *, const char *, FLT_OT_VAR_CTX_SIZE, char **, void *);
#ifndef DEBUG_OT
# define flt_ot_vars_dump(...) while (0)
#else
void flt_ot_vars_dump(struct stream *s);
#endif
int flt_ot_var_register(const char *scope, const char *prefix, const char *name, char **err);
int flt_ot_var_set(struct stream *s, const char *scope, const char *prefix, const char *name, const char *value, uint opt, char **err);
int flt_ot_vars_unset(struct stream *s, const char *scope, const char *prefix, uint opt, char **err);
struct otc_text_map *flt_ot_vars_get(struct stream *s, const char *scope, const char *prefix, uint opt, char **err);
#endif /* _OPENTRACING_VARS_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,397 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
/***
* NAME
* flt_ot_cli_set_msg -
*
* ARGUMENTS
* appctx -
* err -
* msg -
* cli_state -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
static void cmn_cli_set_msg(struct appctx *appctx, char *err, char *msg, int cli_state)
{
struct cli_print_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
FLT_OT_FUNC("%p, %p, %p, %d", appctx, err, msg, cli_state);
if ((appctx == NULL) || ((err == NULL) && (msg == NULL)))
FLT_OT_RETURN();
ctx->err = (err == NULL) ? msg : err;
appctx->st0 = (ctx->err == NULL) ? CLI_ST_PROMPT : cli_state;
FLT_OT_DBG(1, "err(%d): \"%s\"", appctx->st0, ctx->err);
FLT_OT_RETURN();
}
#ifdef DEBUG_OT
/***
* NAME
* flt_ot_cli_parse_debug -
*
* ARGUMENTS
* args -
* payload -
* appctx -
* private -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_cli_parse_debug(char **args, char *payload, struct appctx *appctx, void *private)
{
char *err = NULL, *msg = NULL;
uint8_t value;
int retval = 0;
FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
FLT_OT_ARGS_DUMP();
if (FLT_OT_ARG_ISVALID(2)) {
value = flt_ot_strtoll(args[2], 0, 255, &err);
if (err == NULL) {
_HA_ATOMIC_STORE(&(flt_ot_debug.level), value);
(void)memprintf(&msg, FLT_OT_CLI_CMD " : debug level set to %hhu", value);
} else {
retval = 1;
}
} else {
value = _HA_ATOMIC_LOAD(&(flt_ot_debug.level));
(void)memprintf(&msg, FLT_OT_CLI_CMD " : current debug level is %hhu", value);
}
cmn_cli_set_msg(appctx, err, msg, CLI_ST_PRINT_DYNERR);
FLT_OT_RETURN_INT(retval);
}
#endif /* DEBUG_OT */
/***
* NAME
* flt_ot_cli_parse_disabled -
*
* ARGUMENTS
* args -
* payload -
* appctx -
* private -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_cli_parse_disabled(char **args, char *payload, struct appctx *appctx, void *private)
{
char *msg = NULL;
bool value = (uintptr_t)private;
int retval = 0;
FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
FLT_OT_ARGS_DUMP();
FLT_OT_PROXIES_LIST_START() {
_HA_ATOMIC_STORE(&(conf->tracer->flag_disabled), value);
(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : filter %sabled", FLT_OT_CLI_MSG_CAT(msg), value ? "dis" : "en");
} FLT_OT_PROXIES_LIST_END();
cmn_cli_set_msg(appctx, NULL, msg, CLI_ST_PRINT_DYNERR);
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_cli_parse_option -
*
* ARGUMENTS
* args -
* payload -
* appctx -
* private -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_cli_parse_option(char **args, char *payload, struct appctx *appctx, void *private)
{
char *msg = NULL;
bool value = (uintptr_t)private;
int retval = 0;
FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
FLT_OT_ARGS_DUMP();
FLT_OT_PROXIES_LIST_START() {
_HA_ATOMIC_STORE(&(conf->tracer->flag_harderr), value);
(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : filter set %s-errors", FLT_OT_CLI_MSG_CAT(msg), value ? "hard" : "soft");
} FLT_OT_PROXIES_LIST_END();
cmn_cli_set_msg(appctx, NULL, msg, CLI_ST_PRINT_DYNERR);
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_cli_parse_logging -
*
* ARGUMENTS
* args -
* payload -
* appctx -
* private -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_cli_parse_logging(char **args, char *payload, struct appctx *appctx, void *private)
{
char *err = NULL, *msg = NULL;
uint8_t value;
int retval = 0;
FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
FLT_OT_ARGS_DUMP();
if (FLT_OT_ARG_ISVALID(2)) {
if (strcasecmp(args[2], FLT_OT_CLI_LOGGING_OFF) == 0) {
value = FLT_OT_LOGGING_OFF;
}
else if (strcasecmp(args[2], FLT_OT_CLI_LOGGING_ON) == 0) {
value = FLT_OT_LOGGING_ON;
}
else if (strcasecmp(args[2], FLT_OT_CLI_LOGGING_NOLOGNORM) == 0) {
value = FLT_OT_LOGGING_ON | FLT_OT_LOGGING_NOLOGNORM;
}
else {
(void)memprintf(&err, "'%s' : invalid value, use <" FLT_OT_CLI_LOGGING_OFF " | " FLT_OT_CLI_LOGGING_ON " | " FLT_OT_CLI_LOGGING_NOLOGNORM ">", args[2]);
retval = 1;
}
if (retval == 0) {
FLT_OT_PROXIES_LIST_START() {
_HA_ATOMIC_STORE(&(conf->tracer->logging), value);
(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : logging is %s", FLT_OT_CLI_MSG_CAT(msg), FLT_OT_CLI_LOGGING_STATE(value));
} FLT_OT_PROXIES_LIST_END();
}
} else {
FLT_OT_PROXIES_LIST_START() {
value = _HA_ATOMIC_LOAD(&(conf->tracer->logging));
(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : logging is currently %s", FLT_OT_CLI_MSG_CAT(msg), FLT_OT_CLI_LOGGING_STATE(value));
} FLT_OT_PROXIES_LIST_END();
}
cmn_cli_set_msg(appctx, err, msg, CLI_ST_PRINT_DYNERR);
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_cli_parse_rate -
*
* ARGUMENTS
* args -
* payload -
* appctx -
* private -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_cli_parse_rate(char **args, char *payload, struct appctx *appctx, void *private)
{
char *err = NULL, *msg = NULL;
uint32_t value;
int retval = 0;
FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
FLT_OT_ARGS_DUMP();
if (FLT_OT_ARG_ISVALID(2)) {
value = FLT_OT_FLOAT_U32(flt_ot_strtod(args[2], 0.0, FLT_OT_RATE_LIMIT_MAX, &err), FLT_OT_RATE_LIMIT_MAX);
if (err == NULL) {
FLT_OT_PROXIES_LIST_START() {
_HA_ATOMIC_STORE(&(conf->tracer->rate_limit), value);
(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : rate limit set to %.2f", FLT_OT_CLI_MSG_CAT(msg), FLT_OT_U32_FLOAT(value, FLT_OT_RATE_LIMIT_MAX));
} FLT_OT_PROXIES_LIST_END();
} else {
retval = 1;
}
} else {
FLT_OT_PROXIES_LIST_START() {
value = _HA_ATOMIC_LOAD(&(conf->tracer->rate_limit));
(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : current rate limit is %.2f", FLT_OT_CLI_MSG_CAT(msg), FLT_OT_U32_FLOAT(value, FLT_OT_RATE_LIMIT_MAX));
} FLT_OT_PROXIES_LIST_END();
}
cmn_cli_set_msg(appctx, err, msg, CLI_ST_PRINT_DYNERR);
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_cli_parse_status -
*
* ARGUMENTS
* args -
* payload -
* appctx -
* private -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_cli_parse_status(char **args, char *payload, struct appctx *appctx, void *private)
{
const char *nl = "";
char *msg = NULL;
int retval = 0;
FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
FLT_OT_ARGS_DUMP();
flt_ot_filters_dump();
(void)memprintf(&msg, " " FLT_OT_OPT_NAME " filter status\n" FLT_OT_STR_DASH_78);
#ifdef DEBUG_OT
(void)memprintf(&msg, "%s\n debug level: 0x%02hhx\n", msg, flt_ot_debug.level);
#endif
FLT_OT_PROXIES_LIST_START() {
(void)memprintf(&msg, "%s\n%s filter %s\n", msg, nl, conf->id);
(void)memprintf(&msg, "%s configuration: %s\n", msg, conf->cfg_file);
(void)memprintf(&msg, "%s disable count: %" PRIu64 " %" PRIu64 "\n\n", msg, conf->cnt.disabled[0], conf->cnt.disabled[1]);
(void)memprintf(&msg, "%s tracer %s\n", msg, conf->tracer->id);
(void)memprintf(&msg, "%s configuration: %s\n", msg, conf->tracer->config);
(void)memprintf(&msg, "%s plugin: %s\n", msg, conf->tracer->plugin);
(void)memprintf(&msg, "%s rate limit: %.2f %%\n", msg, FLT_OT_U32_FLOAT(conf->tracer->rate_limit, FLT_OT_RATE_LIMIT_MAX));
(void)memprintf(&msg, "%s hard errors: %s\n", msg, FLT_OT_STR_FLAG_YN(conf->tracer->flag_harderr));
(void)memprintf(&msg, "%s disabled: %s\n", msg, FLT_OT_STR_FLAG_YN(conf->tracer->flag_disabled));
(void)memprintf(&msg, "%s logging: %s\n", msg, FLT_OT_CLI_LOGGING_STATE(conf->tracer->logging));
(void)memprintf(&msg, "%s analyzers: %08x", msg, conf->tracer->analyzers);
nl = "\n";
} FLT_OT_PROXIES_LIST_END();
cmn_cli_set_msg(appctx, NULL, msg, CLI_ST_PRINT_DYNERR);
FLT_OT_RETURN_INT(retval);
}
static struct cli_kw_list cli_kws = { { }, {
#ifdef DEBUG_OT
{ { FLT_OT_CLI_CMD, "debug", NULL }, FLT_OT_CLI_CMD " debug [level] : set the OT filter debug level (default: get current debug level)", flt_ot_cli_parse_debug, NULL, NULL, NULL, 0 },
#endif
{ { FLT_OT_CLI_CMD, "disable", NULL }, FLT_OT_CLI_CMD " disable : disable the OT filter", flt_ot_cli_parse_disabled, NULL, NULL, (void *)1, 0 },
{ { FLT_OT_CLI_CMD, "enable", NULL }, FLT_OT_CLI_CMD " enable : enable the OT filter", flt_ot_cli_parse_disabled, NULL, NULL, (void *)0, 0 },
{ { FLT_OT_CLI_CMD, "soft-errors", NULL }, FLT_OT_CLI_CMD " soft-errors : turning off hard-errors mode", flt_ot_cli_parse_option, NULL, NULL, (void *)0, 0 },
{ { FLT_OT_CLI_CMD, "hard-errors", NULL }, FLT_OT_CLI_CMD " hard-errors : enabling hard-errors mode", flt_ot_cli_parse_option, NULL, NULL, (void *)1, 0 },
{ { FLT_OT_CLI_CMD, "logging", NULL }, FLT_OT_CLI_CMD " logging [state] : set logging state (default: get current logging state)", flt_ot_cli_parse_logging, NULL, NULL, NULL, 0 },
{ { FLT_OT_CLI_CMD, "rate", NULL }, FLT_OT_CLI_CMD " rate [value] : set the rate limit (default: get current rate value)", flt_ot_cli_parse_rate, NULL, NULL, NULL, 0 },
{ { FLT_OT_CLI_CMD, "status", NULL }, FLT_OT_CLI_CMD " status : show the OT filter status", flt_ot_cli_parse_status, NULL, NULL, NULL, 0 },
{ /* END */ }
}};
/***
* NAME
* flt_ot_cli_init -
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_cli_init(void)
{
FLT_OT_FUNC("");
/* Register CLI keywords. */
cli_register_kw(&cli_kws);
FLT_OT_RETURN();
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,764 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
/***
* NAME
* flt_ot_conf_hdr_init -
*
* ARGUMENTS
* size -
* id -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static void *flt_ot_conf_hdr_init(size_t size, const char *id, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_hdr *retptr = NULL, *ptr;
FLT_OT_FUNC("%zu, \"%s\", %d, %p, %p:%p", size, id, linenum, head, FLT_OT_DPTR_ARGS(err));
if (head != NULL)
list_for_each_entry(ptr, head, list)
if (strcmp(ptr->id, id) == 0) {
FLT_OT_ERR("'%s' : already defined", id);
FLT_OT_RETURN_PTR(retptr);
}
retptr = FLT_OT_CALLOC(1, size);
if (retptr != NULL) {
retptr->id_len = strlen(id);
if (retptr->id_len >= FLT_OT_ID_MAXLEN)
FLT_OT_ERR("'%s' : name too long", id);
else
retptr->id = FLT_OT_STRDUP(id);
if (retptr->id == NULL)
FLT_OT_FREE_CLEAR(retptr);
}
if (retptr != NULL) {
retptr->cfg_line = linenum;
if (head != NULL)
LIST_APPEND(head, &(retptr->list));
} else {
FLT_OT_ERR("out of memory");
}
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_ph_init -
*
* ARGUMENTS
* id -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_ph *flt_ot_conf_ph_init(const char *id, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_ph *retptr;
FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
if (retptr != NULL)
FLT_OT_DBG_CONF_PH("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_ph_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_ph_free(struct flt_ot_conf_ph **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_PH("- free ", *ptr);
FLT_OT_FREE((*ptr)->id);
FLT_OT_LIST_DEL(&((*ptr)->list));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_sample_expr_init -
*
* ARGUMENTS
* id -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_sample_expr *flt_ot_conf_sample_expr_init(const char *id, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_sample_expr *retptr;
FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
if (retptr != NULL)
FLT_OT_DBG_CONF_SAMPLE_EXPR("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_sample_expr_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_sample_expr_free(struct flt_ot_conf_sample_expr **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_SAMPLE_EXPR("- free ", *ptr);
FLT_OT_FREE((*ptr)->value);
release_sample_expr((*ptr)->expr);
FLT_OT_LIST_DEL(&((*ptr)->list));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_sample_init -
*
* ARGUMENTS
* args -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_sample *flt_ot_conf_sample_init(char **args, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_sample *retptr;
FLT_OT_FUNC("%p, %d, %p, %p:%p", args, linenum, head, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), args[1], linenum, head, err);
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
flt_ot_args_to_str(args, 2, &(retptr->value));
if (retptr->value == NULL) {
FLT_OT_FREE_CLEAR(retptr);
FLT_OT_RETURN_PTR(retptr);
}
retptr->num_exprs = flt_ot_args_count(args) - 2;
LIST_INIT(&(retptr->exprs));
FLT_OT_DBG_CONF_SAMPLE("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_sample_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_sample_free(struct flt_ot_conf_sample **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_SAMPLE("- free ", *ptr);
FLT_OT_FREE((*ptr)->key);
FLT_OT_FREE((*ptr)->value);
FLT_OT_LIST_DESTROY(sample_expr, &((*ptr)->exprs));
FLT_OT_LIST_DEL(&((*ptr)->list));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_str_init -
*
* ARGUMENTS
* id -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_str *flt_ot_conf_str_init(const char *id, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_str *retptr;
FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
if (retptr != NULL)
FLT_OT_DBG_CONF_STR("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_str_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_str_free(struct flt_ot_conf_str **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_STR("- free ", *ptr);
FLT_OT_FREE((*ptr)->str);
FLT_OT_LIST_DEL(&((*ptr)->list));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_context_init -
*
* ARGUMENTS
* id -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_context *flt_ot_conf_context_init(const char *id, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_context *retptr;
FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
if (retptr != NULL)
FLT_OT_DBG_CONF_CONTEXT("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_context_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_context_free(struct flt_ot_conf_context **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_CONTEXT("- free ", *ptr);
FLT_OT_FREE((*ptr)->id);
FLT_OT_LIST_DEL(&((*ptr)->list));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_span_init -
*
* ARGUMENTS
* id -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_span *flt_ot_conf_span_init(const char *id, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_span *retptr;
FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
LIST_INIT(&(retptr->tags));
LIST_INIT(&(retptr->logs));
LIST_INIT(&(retptr->baggages));
FLT_OT_DBG_CONF_SPAN("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_span_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_span_free(struct flt_ot_conf_span **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_SPAN("- free ", *ptr);
FLT_OT_FREE((*ptr)->id);
FLT_OT_FREE((*ptr)->ref_id);
FLT_OT_FREE((*ptr)->ctx_id);
FLT_OT_LIST_DESTROY(sample, &((*ptr)->tags));
FLT_OT_LIST_DESTROY(sample, &((*ptr)->logs));
FLT_OT_LIST_DESTROY(sample, &((*ptr)->baggages));
FLT_OT_LIST_DEL(&((*ptr)->list));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_scope_init -
*
* ARGUMENTS
* id -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_scope *flt_ot_conf_scope_init(const char *id, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_scope *retptr = NULL;
FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
LIST_INIT(&(retptr->acls));
LIST_INIT(&(retptr->contexts));
LIST_INIT(&(retptr->spans));
LIST_INIT(&(retptr->finish));
FLT_OT_DBG_CONF_SCOPE("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_scope_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_scope_free(struct flt_ot_conf_scope **ptr)
{
struct acl *acl, *aclback;
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_SCOPE("- free ", *ptr);
FLT_OT_FREE((*ptr)->id);
list_for_each_entry_safe(acl, aclback, &((*ptr)->acls), list) {
prune_acl(acl);
FLT_OT_LIST_DEL(&(acl->list));
FLT_OT_FREE(acl);
}
free_acl_cond((*ptr)->cond);
FLT_OT_LIST_DESTROY(context, &((*ptr)->contexts));
FLT_OT_LIST_DESTROY(span, &((*ptr)->spans));
FLT_OT_LIST_DESTROY(str, &((*ptr)->finish));
FLT_OT_LIST_DEL(&((*ptr)->list));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_group_init -
*
* ARGUMENTS
* id -
* linenum -
* head -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_group *flt_ot_conf_group_init(const char *id, int linenum, struct list *head, char **err)
{
struct flt_ot_conf_group *retptr;
FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
LIST_INIT(&(retptr->ph_scopes));
FLT_OT_DBG_CONF_GROUP("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_group_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_group_free(struct flt_ot_conf_group **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_GROUP("- free ", *ptr);
FLT_OT_FREE((*ptr)->id);
FLT_OT_LIST_DESTROY(ph_scope, &((*ptr)->ph_scopes));
FLT_OT_LIST_DEL(&((*ptr)->list));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_tracer_init -
*
* ARGUMENTS
* id -
* linenum -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf_tracer *flt_ot_conf_tracer_init(const char *id, int linenum, char **err)
{
struct flt_ot_conf_tracer *retptr;
FLT_OT_FUNC("\"%s\", %d, %p:%p", id, linenum, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, NULL, err);
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
retptr->rate_limit = FLT_OT_FLOAT_U32(FLT_OT_RATE_LIMIT_MAX, FLT_OT_RATE_LIMIT_MAX);
init_new_proxy(&(retptr->proxy_log));
LIST_INIT(&(retptr->acls));
LIST_INIT(&(retptr->ph_groups));
LIST_INIT(&(retptr->ph_scopes));
FLT_OT_DBG_CONF_TRACER("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_tracer_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_tracer_free(struct flt_ot_conf_tracer **ptr)
{
struct acl *acl, *aclback;
struct logger *logger, *loggerback;
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF_TRACER("- free ", *ptr);
FLT_OT_FREE((*ptr)->id);
FLT_OT_FREE((*ptr)->config);
FLT_OT_FREE((*ptr)->cfgbuf);
FLT_OT_FREE((*ptr)->plugin);
FLT_OT_DBG(2, "- deleting acls list %s", flt_ot_list_debug(&((*ptr)->acls)));
list_for_each_entry_safe(acl, aclback, &((*ptr)->acls), list) {
prune_acl(acl);
FLT_OT_LIST_DEL(&(acl->list));
FLT_OT_FREE(acl);
}
FLT_OT_DBG(2, "- deleting proxy_log.loggers list %s", flt_ot_list_debug(&((*ptr)->proxy_log.loggers)));
list_for_each_entry_safe(logger, loggerback, &((*ptr)->proxy_log.loggers), list) {
LIST_DELETE(&(logger->list));
FLT_OT_FREE(logger);
}
FLT_OT_LIST_DESTROY(ph_group, &((*ptr)->ph_groups));
FLT_OT_LIST_DESTROY(ph_scope, &((*ptr)->ph_scopes));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_conf_init -
*
* ARGUMENTS
* px -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_conf *flt_ot_conf_init(struct proxy *px)
{
struct flt_ot_conf *retptr;
FLT_OT_FUNC("%p", px);
retptr = FLT_OT_CALLOC(1, sizeof(*retptr));
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
retptr->proxy = px;
LIST_INIT(&(retptr->groups));
LIST_INIT(&(retptr->scopes));
FLT_OT_DBG_CONF("- init ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_conf_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_conf_free(struct flt_ot_conf **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_CONF("- free ", *ptr);
FLT_OT_FREE((*ptr)->id);
FLT_OT_FREE((*ptr)->cfg_file);
flt_ot_conf_tracer_free(&((*ptr)->tracer));
FLT_OT_LIST_DESTROY(group, &((*ptr)->groups));
FLT_OT_LIST_DESTROY(scope, &((*ptr)->scopes));
FLT_OT_FREE_CLEAR(*ptr);
FLT_OT_RETURN();
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,338 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
#define FLT_OT_EVENT_DEF(a,b,c,d,e,f) { AN_##b##_##a, SMP_OPT_DIR_##b, SMP_VAL_FE_##c, SMP_VAL_BE_##d, e, f },
const struct flt_ot_event_data flt_ot_event_data[FLT_OT_EVENT_MAX] = { FLT_OT_EVENT_DEFINES };
#undef FLT_OT_EVENT_DEF
/***
* NAME
* flt_ot_scope_run_span -
*
* ARGUMENTS
* s -
* f -
* chn -
* dir -
* span -
* data -
* conf_span -
* ts -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
static int flt_ot_scope_run_span(struct stream *s, struct filter *f, struct channel *chn, uint dir, struct flt_ot_scope_span *span, struct flt_ot_scope_data *data, const struct flt_ot_conf_span *conf_span, const struct timespec *ts, char **err)
{
struct flt_ot_conf *conf = FLT_OT_CONF(f);
int retval = FLT_OT_RET_OK;
FLT_OT_FUNC("%p, %p, %p, %u, %p, %p, %p, %p, %p:%p", s, f, chn, dir, span, data, conf_span, ts, FLT_OT_DPTR_ARGS(err));
if (span == NULL)
FLT_OT_RETURN_INT(retval);
if (span->span == NULL) {
span->span = ot_span_init(conf->tracer->tracer, span->id, ts, NULL, span->ref_type, FLT_OT_DEREF(span->ref_ctx, idx, -1), span->ref_span, data->tags, data->num_tags, err);
if (span->span == NULL)
retval = FLT_OT_RET_ERROR;
}
else if (data->num_tags > 0)
if (ot_span_tag(span->span, data->tags, data->num_tags) == -1)
retval = FLT_OT_RET_ERROR;
if ((span->span != NULL) && (data->baggage != NULL))
if (ot_span_set_baggage(span->span, data->baggage) == -1)
retval = FLT_OT_RET_ERROR;
if ((span->span != NULL) && (data->num_log_fields > 0))
if (ot_span_log(span->span, data->log_fields, data->num_log_fields) == -1)
retval = FLT_OT_RET_ERROR;
if ((span->span != NULL) && (conf_span->ctx_id != NULL)) {
struct otc_http_headers_writer writer;
struct otc_text_map *text_map = NULL;
struct otc_span_context *span_ctx;
span_ctx = ot_inject_http_headers(conf->tracer->tracer, span->span, &writer, err);
if (span_ctx != NULL) {
int i = 0;
if (conf_span->ctx_flags & (FLT_OT_CTX_USE_VARS | FLT_OT_CTX_USE_HEADERS)) {
for (text_map = &(writer.text_map); i < text_map->count; i++) {
#ifdef USE_OT_VARS
if (!(conf_span->ctx_flags & FLT_OT_CTX_USE_VARS))
/* Do nothing. */;
else if (flt_ot_var_register(FLT_OT_VARS_SCOPE, conf_span->ctx_id, text_map->key[i], err) == -1)
retval = FLT_OT_RET_ERROR;
else if (flt_ot_var_set(s, FLT_OT_VARS_SCOPE, conf_span->ctx_id, text_map->key[i], text_map->value[i], dir, err) == -1)
retval = FLT_OT_RET_ERROR;
#endif
if (!(conf_span->ctx_flags & FLT_OT_CTX_USE_HEADERS))
/* Do nothing. */;
else if (flt_ot_http_header_set(chn, conf_span->ctx_id, text_map->key[i], text_map->value[i], err) == -1)
retval = FLT_OT_RET_ERROR;
}
}
span_ctx->destroy(&span_ctx);
otc_text_map_destroy(&text_map, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
}
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_scope_run -
*
* ARGUMENTS
* s -
* f -
* chn -
* conf_scope -
* ts -
* dir -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
int flt_ot_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_ot_conf_scope *conf_scope, const struct timespec *ts, uint dir, char **err)
{
struct flt_ot_conf *conf = FLT_OT_CONF(f);
struct flt_ot_conf_context *conf_ctx;
struct flt_ot_conf_span *conf_span;
struct flt_ot_conf_str *finish;
struct timespec ts_now;
int retval = FLT_OT_RET_OK;
FLT_OT_FUNC("%p, %p, %p, %p, %p, %u, %p:%p", s, f, chn, conf_scope, ts, dir, FLT_OT_DPTR_ARGS(err));
FLT_OT_DBG(3, "channel: %s, mode: %s (%s)", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s));
FLT_OT_DBG(3, "run scope '%s' %d", conf_scope->id, conf_scope->event);
FLT_OT_DBG_CONF_SCOPE("run scope ", conf_scope);
if (ts == NULL) {
(void)clock_gettime(CLOCK_MONOTONIC, &ts_now);
ts = &ts_now;
}
if (conf_scope->cond != NULL) {
enum acl_test_res res;
int rc;
res = acl_exec_cond(conf_scope->cond, s->be, s->sess, s, dir | SMP_OPT_FINAL);
rc = acl_pass(res);
if (conf_scope->cond->pol == ACL_COND_UNLESS)
rc = !rc;
FLT_OT_DBG(3, "the ACL rule %s", rc ? "matches" : "does not match");
/*
* If the rule does not match, the current scope is skipped.
*
* If it is a root span, further processing of the session is
* disabled. As soon as the first span is encountered which
* is marked as root, further search is interrupted.
*/
if (!rc) {
list_for_each_entry(conf_span, &(conf_scope->spans), list)
if (conf_span->flag_root) {
FLT_OT_DBG(0, "session disabled");
FLT_OT_RT_CTX(f->ctx)->flag_disabled = 1;
_HA_ATOMIC_ADD(conf->cnt.disabled + 0, 1);
break;
}
FLT_OT_RETURN_INT(retval);
}
}
list_for_each_entry(conf_ctx, &(conf_scope->contexts), list) {
struct otc_text_map *text_map = NULL;
FLT_OT_DBG(3, "run context '%s' -> '%s'", conf_scope->id, conf_ctx->id);
FLT_OT_DBG_CONF_CONTEXT("run context ", conf_ctx);
/*
* The OpenTracing context is read from the HTTP header
* or from HAProxy variables.
*/
if (conf_ctx->flags & FLT_OT_CTX_USE_HEADERS)
text_map = flt_ot_http_headers_get(chn, conf_ctx->id, conf_ctx->id_len, err);
#ifdef USE_OT_VARS
else
text_map = flt_ot_vars_get(s, FLT_OT_VARS_SCOPE, conf_ctx->id, dir, err);
#endif
if (text_map != NULL) {
if (flt_ot_scope_context_init(f->ctx, conf->tracer->tracer, conf_ctx->id, conf_ctx->id_len, text_map, dir, err) == NULL)
retval = FLT_OT_RET_ERROR;
otc_text_map_destroy(&text_map, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
} else {
retval = FLT_OT_RET_ERROR;
}
}
list_for_each_entry(conf_span, &(conf_scope->spans), list) {
struct flt_ot_scope_data data;
struct flt_ot_scope_span *span;
struct flt_ot_conf_sample *sample;
FLT_OT_DBG(3, "run span '%s' -> '%s'", conf_scope->id, conf_span->id);
FLT_OT_DBG_CONF_SPAN("run span ", conf_span);
(void)memset(&data, 0, sizeof(data));
span = flt_ot_scope_span_init(f->ctx, conf_span->id, conf_span->id_len, conf_span->ref_type, conf_span->ref_id, conf_span->ref_id_len, dir, err);
if (span == NULL)
retval = FLT_OT_RET_ERROR;
list_for_each_entry(sample, &(conf_span->tags), list) {
FLT_OT_DBG(3, "adding tag '%s' -> '%s'", sample->key, sample->value);
if (flt_ot_sample_add(s, dir, sample, &data, FLT_OT_EVENT_SAMPLE_TAG, err) == FLT_OT_RET_ERROR)
retval = FLT_OT_RET_ERROR;
}
list_for_each_entry(sample, &(conf_span->logs), list) {
FLT_OT_DBG(3, "adding log '%s' -> '%s'", sample->key, sample->value);
if (flt_ot_sample_add(s, dir, sample, &data, FLT_OT_EVENT_SAMPLE_LOG, err) == FLT_OT_RET_ERROR)
retval = FLT_OT_RET_ERROR;
}
list_for_each_entry(sample, &(conf_span->baggages), list) {
FLT_OT_DBG(3, "adding baggage '%s' -> '%s'", sample->key, sample->value);
if (flt_ot_sample_add(s, dir, sample, &data, FLT_OT_EVENT_SAMPLE_BAGGAGE, err) == FLT_OT_RET_ERROR)
retval = FLT_OT_RET_ERROR;
}
if (retval != FLT_OT_RET_ERROR)
if (flt_ot_scope_run_span(s, f, chn, dir, span, &data, conf_span, ts, err) == FLT_OT_RET_ERROR)
retval = FLT_OT_RET_ERROR;
flt_ot_scope_data_free(&data);
}
list_for_each_entry(finish, &(conf_scope->finish), list)
if (flt_ot_scope_finish_mark(f->ctx, finish->str, finish->str_len) == -1)
retval = FLT_OT_RET_ERROR;
flt_ot_scope_finish_marked(f->ctx, ts);
flt_ot_scope_free_unused(f->ctx, chn);
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_event_run -
*
* ARGUMENTS
* s -
* f -
* chn -
* event -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
int flt_ot_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err)
{
struct flt_ot_conf *conf = FLT_OT_CONF(f);
struct flt_ot_conf_scope *conf_scope;
struct timespec ts;
int retval = FLT_OT_RET_OK;
FLT_OT_FUNC("%p, %p, %p, %d, %p:%p", s, f, chn, event, FLT_OT_DPTR_ARGS(err));
FLT_OT_DBG(3, "channel: %s, mode: %s (%s)", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s));
FLT_OT_DBG(3, "run event '%s' %d", flt_ot_event_data[event].name, event);
#ifdef DEBUG_OT
_HA_ATOMIC_ADD(conf->cnt.event[event].htx + (htx_is_empty(htxbuf(&(chn->buf))) ? 1 : 0), 1);
#endif
FLT_OT_RT_CTX(f->ctx)->analyzers |= flt_ot_event_data[event].an_bit;
/* All spans should be created/completed at the same time. */
(void)clock_gettime(CLOCK_MONOTONIC, &ts);
/*
* It is possible that there are defined multiple scopes that use the
* same event. Therefore, there must not be a 'break' here, ie an
* exit from the 'for' loop.
*/
list_for_each_entry(conf_scope, &(conf->scopes), list) {
if (conf_scope->event != event)
/* Do nothing. */;
else if (!conf_scope->flag_used)
FLT_OT_DBG(3, "scope '%s' %d not used", conf_scope->id, conf_scope->event);
else if (flt_ot_scope_run(s, f, chn, conf_scope, &ts, flt_ot_event_data[event].smp_opt_dir, err) == FLT_OT_RET_ERROR)
retval = FLT_OT_RET_ERROR;
}
#ifdef USE_OT_VARS
flt_ot_vars_dump(s);
#endif
flt_ot_http_headers_dump(chn);
FLT_OT_DBG(3, "event = %d, chn = %p, s->req = %p, s->res = %p", event, chn, &(s->req), &(s->res));
FLT_OT_RETURN_INT(retval);
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

File diff suppressed because it is too large Load diff

View file

@ -1,354 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
#define FLT_OT_GROUP_DEF(a,b,c) { a, b, c },
const struct flt_ot_group_data flt_ot_group_data[] = { FLT_OT_GROUP_DEFINES };
#undef FLT_OT_GROUP_DEF
/***
* NAME
* flt_ot_group_action -
*
* ARGUMENTS
* rule -
* px -
* sess -
* s -
* opts -
*
* DESCRIPTION
* This is the action_ptr callback of a rule associated to the
* FLT_OT_ACTION_GROUP action.
*
* RETURN VALUE
* The function returns ACT_RET_CONT if processing is finished (with error or
* not), otherwise, it returns ACT_RET_YIELD if the action is in progress.
*/
static enum act_return flt_ot_group_action(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int opts)
{
const struct filter *filter;
const struct flt_conf *fconf;
const struct flt_ot_conf *conf;
const struct flt_ot_conf_group *conf_group;
const struct flt_ot_runtime_context *rt_ctx = NULL;
const struct flt_ot_conf_ph *ph_scope;
char *err = NULL;
int i, rc;
FLT_OT_FUNC("%p, %p, %p, %p, %d", rule, px, sess, s, opts);
FLT_OT_DBG(3, "from: %d, arg.act %p:{ %p %p %p %p }", rule->from, &(rule->arg.act), rule->arg.act.p[0], rule->arg.act.p[1], rule->arg.act.p[2], rule->arg.act.p[3]);
fconf = rule->arg.act.p[FLT_OT_ARG_FLT_CONF];
conf = rule->arg.act.p[FLT_OT_ARG_CONF];
conf_group = ((const struct flt_ot_conf_ph *)(rule->arg.act.p[FLT_OT_ARG_GROUP]))->ptr;
if ((fconf == NULL) || (conf == NULL) || (conf_group == NULL)) {
FLT_OT_LOG(LOG_ERR, FLT_OT_ACTION_GROUP ": internal error, invalid group action");
FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
if (conf->tracer->flag_disabled) {
FLT_OT_DBG(1, "filter '%s' disabled, group action '%s' ignored", conf->id, conf_group->id);
FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
/* Find the OpenTracing filter instance from the current stream. */
list_for_each_entry(filter, &(s->strm_flt.filters), list)
if (filter->config == fconf) {
rt_ctx = filter->ctx;
break;
}
if (rt_ctx == NULL) {
FLT_OT_DBG(1, "cannot find filter, probably not attached to the stream");
FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
else if (flt_ot_is_disabled(filter FLT_OT_DBG_ARGS(, -1))) {
FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
else {
FLT_OT_DBG(3, "run group '%s'", conf_group->id);
FLT_OT_DBG_CONF_GROUP("run group ", conf_group);
}
/*
* Check the value of rule->from; in case it is incorrect,
* report an error.
*/
for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_group_data); i++)
if (flt_ot_group_data[i].act_from == rule->from)
break;
if (i >= FLT_OT_TABLESIZE(flt_ot_group_data)) {
FLT_OT_LOG(LOG_ERR, FLT_OT_ACTION_GROUP ": internal error, invalid rule->from=%d", rule->from);
FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
list_for_each_entry(ph_scope, &(conf_group->ph_scopes), list) {
rc = flt_ot_scope_run(s, rt_ctx->filter, &(s->res), ph_scope->ptr, NULL, SMP_OPT_DIR_RES, &err);
if ((rc == FLT_OT_RET_ERROR) && (opts & ACT_OPT_FINAL)) {
/* XXX */
}
}
FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
/***
* NAME
* flt_ot_group_check -
*
* ARGUMENTS
* rule -
* px -
* err -
*
* DESCRIPTION
* This is the check_ptr callback of a rule associated to the
* FLT_OT_ACTION_GROUP action.
*
* RETURN VALUE
* The function returns 1 in success case, otherwise,
* it returns 0 and err is filled.
*/
static int flt_ot_group_check(struct act_rule *rule, struct proxy *px, char **err)
{
struct flt_conf *fconf_tmp, *fconf = NULL;
struct flt_ot_conf *conf;
struct flt_ot_conf_ph *ph_group;
const char *filter_id;
const char *group_id;
bool flag_found = 0;
int i;
FLT_OT_FUNC("%p, %p, %p:%p", rule, px, FLT_OT_DPTR_ARGS(err));
filter_id = rule->arg.act.p[FLT_OT_ARG_FILTER_ID];
group_id = rule->arg.act.p[FLT_OT_ARG_GROUP_ID];
FLT_OT_DBG(2, "checking filter_id='%s', group_id='%s'", filter_id, group_id);
/*
* Check the value of rule->from; in case it is incorrect,
* report an error.
*/
for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_group_data); i++)
if (flt_ot_group_data[i].act_from == rule->from)
break;
if (i >= FLT_OT_TABLESIZE(flt_ot_group_data)) {
FLT_OT_ERR("internal error, unexpected rule->from=%d, please report this bug!", rule->from);
FLT_OT_RETURN_INT(0);
}
/*
* Try to find the OpenTracing filter by checking all filters
* for the proxy <px>.
*/
list_for_each_entry(fconf_tmp, &(px->filter_configs), list) {
conf = fconf_tmp->conf;
if (fconf_tmp->id != ot_flt_id) {
/* This is not an OpenTracing filter. */
continue;
}
else if (strcmp(conf->id, filter_id) == 0) {
/* This is the good filter ID. */
fconf = fconf_tmp;
break;
}
}
if (fconf == NULL) {
FLT_OT_ERR("unable to find the OpenTracing filter '%s' used by the " FLT_OT_ACTION_GROUP " '%s'", filter_id, group_id);
FLT_OT_RETURN_INT(0);
}
/*
* Attempt to find if the group is defined in the OpenTracing filter
* configuration.
*/
list_for_each_entry(ph_group, &(conf->tracer->ph_groups), list)
if (strcmp(ph_group->id, group_id) == 0) {
flag_found = 1;
break;
}
if (!flag_found) {
FLT_OT_ERR("unable to find group '%s' in the OpenTracing filter '%s' configuration", group_id, filter_id);
FLT_OT_RETURN_INT(0);
}
FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_FILTER_ID]);
FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_GROUP_ID]);
rule->arg.act.p[FLT_OT_ARG_FLT_CONF] = fconf;
rule->arg.act.p[FLT_OT_ARG_CONF] = conf;
rule->arg.act.p[FLT_OT_ARG_GROUP] = ph_group;
FLT_OT_RETURN_INT(1);
}
/***
* NAME
* flt_ot_group_release -
*
* ARGUMENTS
* rule -
*
* DESCRIPTION
* This is the release_ptr callback of a rule associated to the
* FLT_OT_ACTION_GROUP action.
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_ot_group_release(struct act_rule *rule)
{
FLT_OT_FUNC("%p", rule);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_group_parse -
*
* ARGUMENTS
* args -
* cur_arg -
* px -
* rule -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* Returns ACT_RET_PRS_ERR if an error occurs, ACT_RET_PRS_OK otherwise.
*/
static enum act_parse_ret flt_ot_group_parse(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err)
{
FLT_OT_FUNC("%p, %p, %p, %p, %p:%p", args, cur_arg, px, rule, FLT_OT_DPTR_ARGS(err));
if (!FLT_OT_ARG_ISVALID(*cur_arg) ||
!FLT_OT_ARG_ISVALID(*cur_arg + 1) ||
(FLT_OT_ARG_ISVALID(*cur_arg + 2) &&
(strcmp(args[*cur_arg + 2], FLT_OT_CONDITION_IF) != 0) &&
(strcmp(args[*cur_arg + 2], FLT_OT_CONDITION_UNLESS) != 0))) {
FLT_OT_ERR("expects: <filter-id> <group-id> [{ if | unless } ...]");
FLT_OT_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
}
/* Copy the OpenTracing filter id. */
rule->arg.act.p[FLT_OT_ARG_FILTER_ID] = FLT_OT_STRDUP(args[*cur_arg]);
if (rule->arg.act.p[FLT_OT_ARG_FILTER_ID] == NULL) {
FLT_OT_ERR("%s : out of memory", args[*cur_arg]);
FLT_OT_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
}
/* Copy the OpenTracing group id. */
rule->arg.act.p[FLT_OT_ARG_GROUP_ID] = FLT_OT_STRDUP(args[*cur_arg + 1]);
if (rule->arg.act.p[FLT_OT_ARG_GROUP_ID] == NULL) {
FLT_OT_ERR("%s : out of memory", args[*cur_arg + 1]);
FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_FILTER_ID]);
FLT_OT_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
}
rule->action = ACT_CUSTOM;
rule->action_ptr = flt_ot_group_action;
rule->check_ptr = flt_ot_group_check;
rule->release_ptr = flt_ot_group_release;
*cur_arg += 2;
FLT_OT_RETURN_EX(ACT_RET_PRS_OK, enum act_parse_ret, "%d");
}
static struct action_kw_list tcp_req_action_kws = { ILH, {
{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_action_kws);
static struct action_kw_list tcp_res_action_kws = { ILH, {
{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_action_kws);
static struct action_kw_list http_req_action_kws = { ILH, {
{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_action_kws);
static struct action_kw_list http_res_action_kws = { ILH, {
{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_action_kws);
static struct action_kw_list http_after_res_actions_kws = { ILH, {
{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions_kws);
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,312 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
#ifdef DEBUG_OT
/***
* NAME
* flt_ot_http_headers_dump -
*
* ARGUMENTS
* chn -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_http_headers_dump(const struct channel *chn)
{
const struct htx *htx;
int32_t pos;
FLT_OT_FUNC("%p", chn);
if (chn == NULL)
FLT_OT_RETURN();
htx = htxbuf(&(chn->buf));
if (htx_is_empty(htx))
FLT_OT_RETURN();
for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
if (type == HTX_BLK_HDR) {
struct ist n = htx_get_blk_name(htx, blk);
struct ist v = htx_get_blk_value(htx, blk);
FLT_OT_DBG(2, "'%.*s: %.*s'", (int)n.len, n.ptr, (int)v.len, v.ptr);
}
else if (type == HTX_BLK_EOH)
break;
}
FLT_OT_RETURN();
}
#endif /* DEBUG_OT */
/***
* NAME
* flt_ot_http_headers_get -
*
* ARGUMENTS
* chn -
* prefix -
* len -
* err -
*
* DESCRIPTION
* This function is very similar to function http_action_set_header(), from
* the HAProxy source.
*
* RETURN VALUE
* -
*/
struct otc_text_map *flt_ot_http_headers_get(struct channel *chn, const char *prefix, size_t len, char **err)
{
const struct htx *htx;
size_t prefix_len = (!FLT_OT_STR_ISVALID(prefix) || (len == 0)) ? 0 : (len + 1);
int32_t pos;
struct otc_text_map *retptr = NULL;
FLT_OT_FUNC("%p, \"%s\", %zu, %p:%p", chn, prefix, len, FLT_OT_DPTR_ARGS(err));
if (chn == NULL)
FLT_OT_RETURN_PTR(retptr);
/*
* The keyword 'inject' allows you to define the name of the OpenTracing
* context without using a prefix. In that case all HTTP headers are
* transferred because it is not possible to separate them from the
* OpenTracing context (this separation is usually done via a prefix).
*
* When using the 'extract' keyword, the context name must be specified.
* To allow all HTTP headers to be extracted, the first character of
* that name must be set to FLT_OT_PARSE_CTX_IGNORE_NAME.
*/
if (FLT_OT_STR_ISVALID(prefix) && (*prefix == FLT_OT_PARSE_CTX_IGNORE_NAME))
prefix_len = 0;
htx = htxbuf(&(chn->buf));
for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
if (type == HTX_BLK_HDR) {
struct ist v, n = htx_get_blk_name(htx, blk);
if ((prefix_len == 0) || ((n.len >= prefix_len) && (strncasecmp(n.ptr, prefix, len) == 0))) {
if (retptr == NULL) {
retptr = otc_text_map_new(NULL, 8);
if (retptr == NULL) {
FLT_OT_ERR("failed to create HTTP header data");
break;
}
}
v = htx_get_blk_value(htx, blk);
/*
* In case the data of the HTTP header is not
* specified, v.ptr will have some non-null
* value and v.len will be equal to 0. The
* otc_text_map_add() function will not
* interpret this well. In this case v.ptr
* is set to an empty string.
*/
if (v.len == 0)
v = ist("");
/*
* Here, an HTTP header (which is actually part
* of the span context is added to the text_map.
*
* Before adding, the prefix is removed from the
* HTTP header name.
*/
if (otc_text_map_add(retptr, n.ptr + prefix_len, n.len - prefix_len, v.ptr, v.len, OTC_TEXT_MAP_DUP_KEY | OTC_TEXT_MAP_DUP_VALUE) == -1) {
FLT_OT_ERR("failed to add HTTP header data");
otc_text_map_destroy(&retptr, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
break;
}
}
}
else if (type == HTX_BLK_EOH)
break;
}
ot_text_map_show(retptr);
if ((retptr != NULL) && (retptr->count == 0)) {
FLT_OT_DBG(2, "WARNING: no HTTP headers found");
otc_text_map_destroy(&retptr, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
}
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_http_header_set -
*
* ARGUMENTS
* chn -
* prefix -
* name -
* value -
* err -
*
* DESCRIPTION
* This function is very similar to function http_action_set_header(), from
* the HAProxy source.
*
* RETURN VALUE
* -
*/
int flt_ot_http_header_set(struct channel *chn, const char *prefix, const char *name, const char *value, char **err)
{
struct http_hdr_ctx ctx = { .blk = NULL };
struct ist ist_name;
struct buffer *buffer = NULL;
struct htx *htx;
int retval = -1;
FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", %p:%p", chn, prefix, name, value, FLT_OT_DPTR_ARGS(err));
if ((chn == NULL) || (!FLT_OT_STR_ISVALID(prefix) && !FLT_OT_STR_ISVALID(name)))
FLT_OT_RETURN_INT(retval);
htx = htxbuf(&(chn->buf));
/*
* Very rare (about 1% of cases), htx is empty.
* In order to avoid segmentation fault, we exit this function.
*/
if (htx_is_empty(htx)) {
FLT_OT_ERR("HTX is empty");
FLT_OT_RETURN_INT(retval);
}
if (!FLT_OT_STR_ISVALID(prefix)) {
ist_name = ist2((char *)name, strlen(name));
}
else if (!FLT_OT_STR_ISVALID(name)) {
ist_name = ist2((char *)prefix, strlen(prefix));
}
else {
buffer = flt_ot_trash_alloc(0, err);
if (buffer == NULL)
FLT_OT_RETURN_INT(retval);
(void)chunk_printf(buffer, "%s-%s", prefix, name);
ist_name = ist2(buffer->area, buffer->data);
}
/* Remove all occurrences of the header. */
while (http_find_header(htx, ist(""), &ctx, 1) == 1) {
struct ist n = htx_get_blk_name(htx, ctx.blk);
#ifdef DEBUG_OT
struct ist v = htx_get_blk_value(htx, ctx.blk);
#endif
/*
* If the <name> parameter is not set, then remove all headers
* that start with the contents of the <prefix> parameter.
*/
if (!FLT_OT_STR_ISVALID(name))
n.len = ist_name.len;
if (isteqi(n, ist_name))
if (http_remove_header(htx, &ctx) == 1)
FLT_OT_DBG(3, "HTTP header '%.*s: %.*s' removed", (int)n.len, n.ptr, (int)v.len, v.ptr);
}
/*
* If the value pointer has a value of NULL, the HTTP header is not set
* after deletion.
*/
if (value == NULL) {
/* Do nothing. */
}
else if (http_add_header(htx, ist_name, ist(value), 1) == 1) {
retval = 0;
FLT_OT_DBG(3, "HTTP header '%s: %s' added", ist_name.ptr, value);
}
else {
FLT_OT_ERR("failed to set HTTP header '%s: %s'", ist_name.ptr, value);
}
flt_ot_trash_free(&buffer);
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_http_headers_remove -
*
* ARGUMENTS
* chn -
* prefix -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int flt_ot_http_headers_remove(struct channel *chn, const char *prefix, char **err)
{
int retval;
FLT_OT_FUNC("%p, \"%s\", %p:%p", chn, prefix, FLT_OT_DPTR_ARGS(err));
retval = flt_ot_http_header_set(chn, prefix, NULL, NULL, err);
FLT_OT_RETURN_INT(retval);
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,223 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
/***
* NAME
* flt_ot_pool_alloc -
*
* ARGUMENTS
* pool -
* size -
* flag_clear -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
void *flt_ot_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err)
{
void *retptr;
FLT_OT_FUNC("%p, %zu, %hhu, %p:%p", pool, size, flag_clear, FLT_OT_DPTR_ARGS(err));
if (pool != NULL) {
retptr = pool_alloc(pool);
if (retptr != NULL)
FLT_OT_DBG(2, "POOL_ALLOC: %s:%d(%p %zu)", __func__, __LINE__, retptr, FLT_OT_DEREF(pool, size, size));
} else {
retptr = FLT_OT_MALLOC(size);
}
if (retptr == NULL)
FLT_OT_ERR("out of memory");
else if (flag_clear)
(void)memset(retptr, 0, size);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_pool_strndup -
*
* ARGUMENTS
* pool -
* s -
* size -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
void *flt_ot_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err)
{
void *retptr;
FLT_OT_FUNC("%p, \"%.*s\", %zu, %p:%p", pool, (int)size, s, size, FLT_OT_DPTR_ARGS(err));
if (pool != NULL) {
retptr = pool_alloc(pool);
if (retptr != NULL) {
(void)memcpy(retptr, s, MIN(pool->size - 1, size));
((uint8_t *)retptr)[MIN(pool->size - 1, size)] = '\0';
}
} else {
retptr = FLT_OT_STRNDUP(s, size);
}
if (retptr != NULL)
FLT_OT_DBG(2, "POOL_STRNDUP: %s:%d(%p %zu)", __func__, __LINE__, retptr, FLT_OT_DEREF(pool, size, size));
else
FLT_OT_ERR("out of memory");
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_pool_free -
*
* ARGUMENTS
* pool -
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_pool_free(struct pool_head *pool, void **ptr)
{
FLT_OT_FUNC("%p, %p:%p", pool, FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG(2, "POOL_FREE: %s:%d(%p %u)", __func__, __LINE__, *ptr, FLT_OT_DEREF(pool, size, 0));
if (pool != NULL)
pool_free(pool, *ptr);
else
FLT_OT_FREE(*ptr);
*ptr = NULL;
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_trash_alloc -
*
* ARGUMENTS
* flag_clear -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct buffer *flt_ot_trash_alloc(bool flag_clear, char **err)
{
struct buffer *retptr;
FLT_OT_FUNC("%hhu, %p:%p", flag_clear, FLT_OT_DPTR_ARGS(err));
#ifdef USE_TRASH_CHUNK
retptr = alloc_trash_chunk();
if (retptr != NULL)
FLT_OT_DBG(2, "TRASH_ALLOC: %s:%d(%p %zu)", __func__, __LINE__, retptr, retptr->size);
#else
retptr = FLT_OT_MALLOC(sizeof(*retptr));
if (retptr != NULL) {
chunk_init(retptr, FLT_OT_MALLOC(global.tune.bufsize), global.tune.bufsize);
if (retptr->area == NULL)
FLT_OT_FREE_CLEAR(retptr);
else
*(retptr->area) = '\0';
}
#endif
if (retptr == NULL)
FLT_OT_ERR("out of memory");
else if (flag_clear)
(void)memset(retptr->area, 0, retptr->size);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_trash_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_trash_free(struct buffer **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG(2, "TRASH_FREE: %s:%d(%p %zu)", __func__, __LINE__, *ptr, (*ptr)->size);
#ifdef USE_TRASH_CHUNK
free_trash_chunk(*ptr);
#else
FLT_OT_FREE((*ptr)->area);
FLT_OT_FREE(*ptr);
#endif
*ptr = NULL;
FLT_OT_RETURN();
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,634 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
static struct pool_head *pool_head_ot_scope_span __read_mostly = NULL;
static struct pool_head *pool_head_ot_scope_context __read_mostly = NULL;
static struct pool_head *pool_head_ot_runtime_context __read_mostly = NULL;
#ifdef USE_POOL_OT_SCOPE_SPAN
REGISTER_POOL(&pool_head_ot_scope_span, "ot_scope_span", sizeof(struct flt_ot_scope_span));
#endif
#ifdef USE_POOL_OT_SCOPE_CONTEXT
REGISTER_POOL(&pool_head_ot_scope_context, "ot_scope_context", sizeof(struct flt_ot_scope_context));
#endif
#ifdef USE_POOL_OT_RUNTIME_CONTEXT
REGISTER_POOL(&pool_head_ot_runtime_context, "ot_runtime_context", sizeof(struct flt_ot_runtime_context));
#endif
#ifdef DEBUG_OT
/***
* NAME
* flt_ot_pools_info -
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_pools_info(void)
{
/*
* In case we have some error in the configuration file,
* it is possible that this pool was not initialized.
*/
#ifdef USE_POOL_BUFFER
FLT_OT_DBG(2, "sizeof_pool(buffer) = %u", FLT_OT_DEREF(pool_head_buffer, size, 0));
#endif
#ifdef USE_TRASH_CHUNK
FLT_OT_DBG(2, "sizeof_pool(trash) = %u", FLT_OT_DEREF(pool_head_trash, size, 0));
#endif
#ifdef USE_POOL_OT_SCOPE_SPAN
FLT_OT_DBG(2, "sizeof_pool(ot_scope_span) = %u", pool_head_ot_scope_span->size);
#endif
#ifdef USE_POOL_OT_SCOPE_CONTEXT
FLT_OT_DBG(2, "sizeof_pool(ot_scope_context) = %u", pool_head_ot_scope_context->size);
#endif
#ifdef USE_POOL_OT_RUNTIME_CONTEXT
FLT_OT_DBG(2, "sizeof_pool(ot_runtime_context) = %u", pool_head_ot_runtime_context->size);
#endif
}
#endif /* DEBUG_OT */
/***
* NAME
* flt_ot_runtime_context_init -
*
* ARGUMENTS
* s -
* f -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_runtime_context *flt_ot_runtime_context_init(struct stream *s, struct filter *f, char **err)
{
const struct flt_ot_conf *conf = FLT_OT_CONF(f);
struct buffer uuid;
struct flt_ot_runtime_context *retptr = NULL;
FLT_OT_FUNC("%p, %p, %p:%p", s, f, FLT_OT_DPTR_ARGS(err));
retptr = flt_ot_pool_alloc(pool_head_ot_runtime_context, sizeof(*retptr), 1, err);
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
retptr->stream = s;
retptr->filter = f;
retptr->flag_harderr = conf->tracer->flag_harderr;
retptr->flag_disabled = conf->tracer->flag_disabled;
retptr->logging = conf->tracer->logging;
LIST_INIT(&(retptr->spans));
LIST_INIT(&(retptr->contexts));
uuid = b_make(retptr->uuid, sizeof(retptr->uuid), 0, 0);
ha_generate_uuid_v4(&uuid);
#ifdef USE_OT_VARS
/*
* The HAProxy variable 'sess.ot.uuid' is registered here,
* after which its value is set to runtime context UUID.
*/
if (flt_ot_var_register(FLT_OT_VAR_UUID, err) != -1)
(void)flt_ot_var_set(s, FLT_OT_VAR_UUID, retptr->uuid, SMP_OPT_DIR_REQ, err);
#endif
FLT_OT_DBG_RUNTIME_CONTEXT("session context: ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_runtime_context_free -
*
* ARGUMENTS
* f -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_runtime_context_free(struct filter *f)
{
struct flt_ot_runtime_context *rt_ctx = f->ctx;
FLT_OT_FUNC("%p", f);
if (rt_ctx == NULL)
FLT_OT_RETURN();
FLT_OT_DBG_RUNTIME_CONTEXT("session context: ", rt_ctx);
if (!LIST_ISEMPTY(&(rt_ctx->spans))) {
struct timespec ts;
struct flt_ot_scope_span *span, *span_back;
/* All spans should be completed at the same time. */
(void)clock_gettime(CLOCK_MONOTONIC, &ts);
list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list) {
ot_span_finish(&(span->span), &ts, NULL, NULL, NULL);
flt_ot_scope_span_free(&span);
}
}
if (!LIST_ISEMPTY(&(rt_ctx->contexts))) {
struct flt_ot_scope_context *ctx, *ctx_back;
list_for_each_entry_safe(ctx, ctx_back, &(rt_ctx->contexts), list)
flt_ot_scope_context_free(&ctx);
}
flt_ot_pool_free(pool_head_ot_runtime_context, &(f->ctx));
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_scope_span_init -
*
* ARGUMENTS
* rt_ctx -
* id -
* id_len -
* ref_type -
* ref_id -
* ref_id_len -
* dir -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_scope_span *flt_ot_scope_span_init(struct flt_ot_runtime_context *rt_ctx, const char *id, size_t id_len, otc_span_reference_type_t ref_type, const char *ref_id, size_t ref_id_len, uint dir, char **err)
{
struct otc_span *ref_span = NULL;
struct otc_span_context *ref_ctx = NULL;
struct flt_ot_scope_span *span, *retptr = NULL;
struct flt_ot_scope_context *ctx;
FLT_OT_FUNC("%p, \"%s\", %zu, %d, \"%s\", %zu, %u, %p:%p", rt_ctx, id, id_len, ref_type, ref_id, ref_id_len, dir, FLT_OT_DPTR_ARGS(err));
if ((rt_ctx == NULL) || (id == NULL))
FLT_OT_RETURN_PTR(retptr);
list_for_each_entry(span, &(rt_ctx->spans), list)
if ((span->id_len == id_len) && (memcmp(span->id, id, id_len) == 0)) {
FLT_OT_DBG(2, "found span %p", span);
FLT_OT_RETURN_PTR(span);
}
if (ref_id != NULL) {
list_for_each_entry(span, &(rt_ctx->spans), list)
if ((span->id_len == ref_id_len) && (memcmp(span->id, ref_id, ref_id_len) == 0)) {
ref_span = span->span;
break;
}
if (ref_span != NULL) {
FLT_OT_DBG(2, "found referenced span %p", span);
} else {
list_for_each_entry(ctx, &(rt_ctx->contexts), list)
if ((ctx->id_len == ref_id_len) && (memcmp(ctx->id, ref_id, ref_id_len) == 0)) {
ref_ctx = ctx->context;
break;
}
if (ref_ctx != NULL) {
FLT_OT_DBG(2, "found referenced context %p", ctx);
} else {
FLT_OT_ERR("cannot find referenced span/context '%s'", ref_id);
FLT_OT_RETURN_PTR(retptr);
}
}
}
retptr = flt_ot_pool_alloc(pool_head_ot_scope_span, sizeof(*retptr), 1, err);
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
retptr->id = id;
retptr->id_len = id_len;
retptr->smp_opt_dir = dir;
retptr->ref_type = ref_type;
retptr->ref_span = ref_span;
retptr->ref_ctx = ref_ctx;
LIST_INSERT(&(rt_ctx->spans), &(retptr->list));
FLT_OT_DBG_SCOPE_SPAN("new span ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_scope_span_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_scope_span_free(struct flt_ot_scope_span **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_SCOPE_SPAN("", *ptr);
/* If the span is still active, do nothing. */
if ((*ptr)->span != NULL) {
FLT_OT_DBG(2, "cannot finish active span");
FLT_OT_RETURN();
}
FLT_OT_LIST_DEL(&((*ptr)->list));
flt_ot_pool_free(pool_head_ot_scope_span, (void **)ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_scope_context_init -
*
* ARGUMENTS
* rt_ctx -
* tracer -
* id -
* id_len -
* text_map -
* dir -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct flt_ot_scope_context *flt_ot_scope_context_init(struct flt_ot_runtime_context *rt_ctx, struct otc_tracer *tracer, const char *id, size_t id_len, const struct otc_text_map *text_map, uint dir, char **err)
{
struct otc_http_headers_reader reader;
struct otc_span_context *span_ctx;
struct flt_ot_scope_context *retptr = NULL;
FLT_OT_FUNC("%p, %p, \"%s\", %zu, %p, %u, %p:%p", rt_ctx, tracer, id, id_len, text_map, dir, FLT_OT_DPTR_ARGS(err));
if ((rt_ctx == NULL) || (tracer == NULL) || (id == NULL) || (text_map == NULL))
FLT_OT_RETURN_PTR(retptr);
list_for_each_entry(retptr, &(rt_ctx->contexts), list)
if ((retptr->id_len == id_len) && (memcmp(retptr->id, id, id_len) == 0)) {
FLT_OT_DBG(2, "found context %p", retptr);
FLT_OT_RETURN_PTR(retptr);
}
retptr = flt_ot_pool_alloc(pool_head_ot_scope_context, sizeof(*retptr), 1, err);
if (retptr == NULL)
FLT_OT_RETURN_PTR(retptr);
span_ctx = ot_extract_http_headers(tracer, &reader, text_map, err);
if (span_ctx == NULL) {
flt_ot_scope_context_free(&retptr);
FLT_OT_RETURN_PTR(retptr);
}
retptr->id = id;
retptr->id_len = id_len;
retptr->smp_opt_dir = dir;
retptr->context = span_ctx;
LIST_INSERT(&(rt_ctx->contexts), &(retptr->list));
FLT_OT_DBG_SCOPE_CONTEXT("new context ", retptr);
FLT_OT_RETURN_PTR(retptr);
}
/***
* NAME
* flt_ot_scope_context_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_scope_context_free(struct flt_ot_scope_context **ptr)
{
FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
FLT_OT_RETURN();
FLT_OT_DBG_SCOPE_CONTEXT("", *ptr);
if ((*ptr)->context != NULL)
(*ptr)->context->destroy(&((*ptr)->context));
FLT_OT_LIST_DEL(&((*ptr)->list));
flt_ot_pool_free(pool_head_ot_scope_context, (void **)ptr);
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_scope_data_free -
*
* ARGUMENTS
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_scope_data_free(struct flt_ot_scope_data *ptr)
{
int i;
FLT_OT_FUNC("%p", ptr);
if (ptr == NULL)
FLT_OT_RETURN();
FLT_OT_DBG_SCOPE_DATA("", ptr);
for (i = 0; i < ptr->num_tags; i++)
if (ptr->tags[i].value.type == otc_value_string)
FLT_OT_FREE_VOID(ptr->tags[i].value.value.string_value);
otc_text_map_destroy(&(ptr->baggage), OTC_TEXT_MAP_FREE_VALUE);
for (i = 0; i < ptr->num_log_fields; i++)
if (ptr->log_fields[i].value.type == otc_value_string)
FLT_OT_FREE_VOID(ptr->log_fields[i].value.value.string_value);
(void)memset(ptr, 0, sizeof(*ptr));
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_scope_finish_mark -
*
* ARGUMENTS
* rt_ctx -
* id -
* id_len -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int flt_ot_scope_finish_mark(const struct flt_ot_runtime_context *rt_ctx, const char *id, size_t id_len)
{
struct flt_ot_scope_span *span;
struct flt_ot_scope_context *ctx;
int span_cnt = 0, ctx_cnt = 0, retval;
FLT_OT_FUNC("%p, \"%s\", %zu", rt_ctx, id, id_len);
if (FLT_OT_STR_CMP(FLT_OT_SCOPE_SPAN_FINISH_ALL, id, id_len)) {
list_for_each_entry(span, &(rt_ctx->spans), list) {
span->flag_finish = 1;
span_cnt++;
}
list_for_each_entry(ctx, &(rt_ctx->contexts), list) {
ctx->flag_finish = 1;
ctx_cnt++;
}
FLT_OT_DBG(2, "marked %d span(s), %d context(s)", span_cnt, ctx_cnt);
}
else if (FLT_OT_STR_CMP(FLT_OT_SCOPE_SPAN_FINISH_REQ, id, id_len)) {
list_for_each_entry(span, &(rt_ctx->spans), list)
if (span->smp_opt_dir == SMP_OPT_DIR_REQ) {
span->flag_finish = 1;
span_cnt++;
}
list_for_each_entry(ctx, &(rt_ctx->contexts), list)
if (ctx->smp_opt_dir == SMP_OPT_DIR_REQ) {
ctx->flag_finish = 1;
span_cnt++;
}
FLT_OT_DBG(2, "marked REQuest channel %d span(s), %d context(s)", span_cnt, ctx_cnt);
}
else if (FLT_OT_STR_CMP(FLT_OT_SCOPE_SPAN_FINISH_RES, id, id_len)) {
list_for_each_entry(span, &(rt_ctx->spans), list)
if (span->smp_opt_dir == SMP_OPT_DIR_RES) {
span->flag_finish = 1;
span_cnt++;
}
list_for_each_entry(ctx, &(rt_ctx->contexts), list)
if (ctx->smp_opt_dir == SMP_OPT_DIR_RES) {
ctx->flag_finish = 1;
ctx_cnt++;
}
FLT_OT_DBG(2, "marked RESponse channel %d span(s), %d context(s)", span_cnt, ctx_cnt);
}
else {
list_for_each_entry(span, &(rt_ctx->spans), list)
if ((span->id_len == id_len) && (memcmp(span->id, id, id_len) == 0)) {
span->flag_finish = 1;
span_cnt++;
break;
}
list_for_each_entry(ctx, &(rt_ctx->contexts), list)
if ((ctx->id_len == id_len) && (memcmp(ctx->id, id, id_len) == 0)) {
ctx->flag_finish = 1;
ctx_cnt++;
break;
}
if (span_cnt > 0)
FLT_OT_DBG(2, "marked span '%s'", id);
if (ctx_cnt > 0)
FLT_OT_DBG(2, "marked context '%s'", id);
if ((span_cnt + ctx_cnt) == 0)
FLT_OT_DBG(2, "cannot find span/context '%s'", id);
}
retval = span_cnt + ctx_cnt;
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_scope_finish_marked -
*
* ARGUMENTS
* rt_ctx -
* ts_finish -
*
* DESCRIPTION
* Finish marked spans.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_scope_finish_marked(const struct flt_ot_runtime_context *rt_ctx, const struct timespec *ts_finish)
{
struct flt_ot_scope_span *span;
struct flt_ot_scope_context *ctx;
FLT_OT_FUNC("%p, %p", rt_ctx, ts_finish);
list_for_each_entry(span, &(rt_ctx->spans), list)
if (span->flag_finish) {
FLT_OT_DBG_SCOPE_SPAN("finishing span ", span);
ot_span_finish(&(span->span), ts_finish, NULL, NULL, NULL);
span->flag_finish = 0;
}
list_for_each_entry(ctx, &(rt_ctx->contexts), list)
if (ctx->flag_finish) {
FLT_OT_DBG_SCOPE_CONTEXT("finishing context ", ctx);
if (ctx->context != NULL)
ctx->context->destroy(&(ctx->context));
ctx->flag_finish = 0;
}
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_scope_free_unused -
*
* ARGUMENTS
* rt_ctx -
* chn -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_scope_free_unused(struct flt_ot_runtime_context *rt_ctx, struct channel *chn)
{
FLT_OT_FUNC("%p", rt_ctx);
if (rt_ctx == NULL)
FLT_OT_RETURN();
if (!LIST_ISEMPTY(&(rt_ctx->spans))) {
struct flt_ot_scope_span *span, *span_back;
list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list)
if (span->span == NULL)
flt_ot_scope_span_free(&span);
}
if (!LIST_ISEMPTY(&(rt_ctx->contexts))) {
struct flt_ot_scope_context *ctx, *ctx_back;
list_for_each_entry_safe(ctx, ctx_back, &(rt_ctx->contexts), list)
if (ctx->context == NULL) {
/*
* All headers and variables associated with
* the context in question should be deleted.
*/
(void)flt_ot_http_headers_remove(chn, ctx->id, NULL);
#ifdef USE_OT_VARS
(void)flt_ot_vars_unset(rt_ctx->stream, FLT_OT_VARS_SCOPE, ctx->id, ctx->smp_opt_dir, NULL);
#endif
flt_ot_scope_context_free(&ctx);
}
}
FLT_OT_DBG_RUNTIME_CONTEXT("session context: ", rt_ctx);
FLT_OT_RETURN();
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,815 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
#ifdef DEBUG_OT
/***
* NAME
* flt_ot_args_dump -
*
* ARGUMENTS
* args -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_args_dump(char **args)
{
int i, argc;
argc = flt_ot_args_count(args);
(void)fprintf(stderr, FLT_OT_DBG_FMT("%.*sargs[%d]: { '%s' "), flt_ot_dbg_indent_level, FLT_OT_DBG_INDENT, argc, args[0]);
for (i = 1; i < argc; i++)
(void)fprintf(stderr, "'%s' ", args[i]);
(void)fprintf(stderr, "}\n");
}
/***
* NAME
* flt_ot_filters_dump -
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_filters_dump(void)
{
struct flt_conf *fconf;
struct proxy *px;
FLT_OT_FUNC("");
for (px = proxies_list; px != NULL; px = px->next) {
FLT_OT_DBG(2, "proxy '%s'", px->id);
list_for_each_entry(fconf, &(px->filter_configs), list)
if (fconf->id == ot_flt_id) {
struct flt_ot_conf *conf = fconf->conf;
FLT_OT_DBG(2, " OT filter '%s'", conf->id);
}
}
FLT_OT_RETURN();
}
/***
* NAME
* flt_ot_chn_label -
*
* ARGUMENTS
* chn -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
const char *flt_ot_chn_label(const struct channel *chn)
{
return (chn->flags & CF_ISRESP) ? "RESponse" : "REQuest";
}
/***
* NAME
* flt_ot_pr_mode -
*
* ARGUMENTS
* s -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
const char *flt_ot_pr_mode(const struct stream *s)
{
struct proxy *px = (s->flags & SF_BE_ASSIGNED) ? s->be : strm_fe(s);
return (px->mode == PR_MODE_HTTP) ? "HTTP" : "TCP";
}
/***
* NAME
* flt_ot_stream_pos -
*
* ARGUMENTS
* s -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
const char *flt_ot_stream_pos(const struct stream *s)
{
return (s->flags & SF_BE_ASSIGNED) ? "backend" : "frontend";
}
/***
* NAME
* flt_ot_type -
*
* ARGUMENTS
* f -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
const char *flt_ot_type(const struct filter *f)
{
return (f->flags & FLT_FL_IS_BACKEND_FILTER) ? "backend" : "frontend";
}
/***
* NAME
* flt_ot_analyzer -
*
* ARGUMENTS
* an_bit -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
const char *flt_ot_analyzer(uint an_bit)
{
#define FLT_OT_AN_DEF(a) { a, #a },
static const struct {
uint an_bit;
const char *str;
} flt_ot_an[] = { FLT_OT_AN_DEFINES };
#undef FLT_OT_AN_DEF
const char *retptr = "invalid an_bit";
int i;
for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_an); i++)
if (flt_ot_an[i].an_bit == an_bit) {
retptr = flt_ot_an[i].str;
break;
}
return retptr;
}
/***
* NAME
* flt_ot_str_hex -
*
* ARGUMENTS
* data -
* size -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
const char *flt_ot_str_hex(const void *data, size_t size)
{
static THREAD_LOCAL char retbuf[BUFSIZ];
const uint8_t *ptr = data;
size_t i;
if (data == NULL)
return "(null)";
else if (size == 0)
return "()";
for (i = 0, size <<= 1; (i < (sizeof(retbuf) - 2)) && (i < size); ptr++) {
retbuf[i++] = FLT_OT_NIBBLE_TO_HEX(*ptr >> 4);
retbuf[i++] = FLT_OT_NIBBLE_TO_HEX(*ptr & 0x0f);
}
retbuf[i] = '\0';
return retbuf;
}
/***
* NAME
* flt_ot_str_ctrl -
*
* ARGUMENTS
* data -
* size -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
const char *flt_ot_str_ctrl(const void *data, size_t size)
{
static THREAD_LOCAL char retbuf[BUFSIZ];
const uint8_t *ptr = data;
size_t i, n = 0;
if (data == NULL)
return "(null)";
else if (size == 0)
return "()";
for (i = 0; (n < (sizeof(retbuf) - 1)) && (i < size); i++)
retbuf[n++] = ((ptr[i] >= 0x20) && (ptr[i] <= 0x7e)) ? ptr[i] : '.';
retbuf[n] = '\0';
return retbuf;
}
/***
* NAME
* flt_ot_list_debug -
*
* ARGUMENTS
* head -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
const char *flt_ot_list_debug(const struct list *head)
{
FLT_OT_BUFFER_THR(retbuf, 4, 64, retptr);
if ((head == NULL) || LIST_ISEMPTY(head)) {
(void)strncpy(retptr, (head == NULL) ? "{ null list }" : "{ empty list }", sizeof(retbuf[0]));
}
else if (head->p == head->n) {
(void)snprintf(retptr, sizeof(retbuf[0]), "{ %p * 1 }", head->p);
}
else {
const struct list *ptr;
size_t count = 0;
for (ptr = head->n; ptr != head; ptr = ptr->n, count++);
(void)snprintf(retptr, sizeof(retbuf[0]), "{ %p %p %zu }", head->p, head->n, count);
}
return (retptr);
}
#endif /* DEBUG_OT */
/***
* NAME
* flt_ot_chunk_add -
*
* ARGUMENTS
* chk -
* src -
* n -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
ssize_t flt_ot_chunk_add(struct buffer *chk, const void *src, size_t n, char **err)
{
FLT_OT_FUNC("%p, %p, %zu, %p:%p", chk, src, n, FLT_OT_DPTR_ARGS(err));
if ((chk == NULL) || (src == NULL))
FLT_OT_RETURN_EX(-1, ssize_t, "%ld");
if (chk->area == NULL)
chunk_init(chk, FLT_OT_CALLOC(1, global.tune.bufsize), global.tune.bufsize);
if (chk->area == NULL) {
FLT_OT_ERR("out of memory");
FLT_OT_RETURN_EX(-1, ssize_t, "%ld");
}
else if (n > (chk->size - chk->data)) {
FLT_OT_ERR("chunk size too small");
FLT_OT_RETURN_EX(-1, ssize_t, "%ld");
}
(void)memcpy(chk->area + chk->data, src, n);
chk->data += n;
FLT_OT_RETURN_EX(chk->data, ssize_t, "%ld");
}
/***
* NAME
* flt_ot_args_count -
*
* ARGUMENTS
* args -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int flt_ot_args_count(char **args)
{
int i, retval = 0;
if (args == NULL)
return retval;
/*
* It is possible that some arguments within the configuration line
* are not specified; that is, they are set to a blank string.
*
* For example:
* keyword '' arg_2
*
* In that case the content of the args field will be like this:
* args[0]: 'keyword'
* args[1]: NULL pointer
* args[2]: 'arg_2'
* args[3 .. MAX_LINE_ARGS): NULL pointers
*
* The total number of arguments is the index of the last argument
* (increased by 1) that is not a NULL pointer.
*/
for (i = 0; i < MAX_LINE_ARGS; i++)
if (FLT_OT_ARG_ISVALID(i))
retval = i + 1;
return retval;
}
/***
* NAME
* flt_ot_args_to_str -
*
* ARGUMENTS
* args -
* idx -
* str -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_args_to_str(char **args, int idx, char **str)
{
int i, argc;
if ((args == NULL) || (*args == NULL))
return;
argc = flt_ot_args_count(args);
for (i = idx; i < argc; i++)
(void)memprintf(str, "%s%s%s", (*str == NULL) ? "" : *str, (i == idx) ? "" : " ", (args[i] == NULL) ? "" : args[i]);
}
/***
* NAME
* flt_ot_strtod -
*
* ARGUMENTS
* nptr -
* limit_min -
* limit_max -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
double flt_ot_strtod(const char *nptr, double limit_min, double limit_max, char **err)
{
char *endptr = NULL;
double retval;
errno = 0;
retval = strtod(nptr, &endptr);
if ((errno != 0) || FLT_OT_STR_ISVALID(endptr))
FLT_OT_ERR("'%s' : invalid value", nptr);
else if (!FLT_OT_IN_RANGE(retval, limit_min, limit_max))
FLT_OT_ERR("'%s' : value out of range [%.2f, %.2f]", nptr, limit_min, limit_max);
return retval;
}
/***
* NAME
* flt_ot_strtoll -
*
* ARGUMENTS
* nptr -
* limit_min -
* limit_max -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int64_t flt_ot_strtoll(const char *nptr, int64_t limit_min, int64_t limit_max, char **err)
{
char *endptr = NULL;
int64_t retval;
errno = 0;
retval = strtoll(nptr, &endptr, 0);
if ((errno != 0) || FLT_OT_STR_ISVALID(endptr))
FLT_OT_ERR("'%s' : invalid value", nptr);
else if (!FLT_OT_IN_RANGE(retval, limit_min, limit_max))
FLT_OT_ERR("'%s' : value out of range [%" PRId64 ", %" PRId64 "]", nptr, limit_min, limit_max);
return retval;
}
/***
* NAME
* flt_ot_sample_to_str -
*
* ARGUMENTS
* data -
* value -
* size -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int flt_ot_sample_to_str(const struct sample_data *data, char *value, size_t size, char **err)
{
int retval = -1;
FLT_OT_FUNC("%p, %p, %zu, %p:%p", data, value, size, FLT_OT_DPTR_ARGS(err));
if ((data == NULL) || (value == NULL) || (size == 0))
FLT_OT_RETURN_INT(retval);
*value = '\0';
if (data->type == SMP_T_ANY) {
FLT_OT_ERR("invalid sample data type %d", data->type);
}
else if (data->type == SMP_T_BOOL) {
value[0] = data->u.sint ? '1' : '0';
value[1] = '\0';
retval = 1;
}
else if (data->type == SMP_T_SINT) {
retval = snprintf(value, size, "%lld", data->u.sint);
}
else if (data->type == SMP_T_ADDR) {
/* This type is never used to qualify a sample. */
}
else if (data->type == SMP_T_IPV4) {
if (INET_ADDRSTRLEN > size)
FLT_OT_ERR("sample data size too large");
else if (inet_ntop(AF_INET, &(data->u.ipv4), value, INET_ADDRSTRLEN) == NULL)
FLT_OT_ERR("invalid IPv4 address");
else
retval = strlen(value);
}
else if (data->type == SMP_T_IPV6) {
if (INET6_ADDRSTRLEN > size)
FLT_OT_ERR("sample data size too large");
else if (inet_ntop(AF_INET6, &(data->u.ipv6), value, INET6_ADDRSTRLEN) == NULL)
FLT_OT_ERR("invalid IPv6 address");
else
retval = strlen(value);
}
else if (data->type == SMP_T_STR) {
if (data->u.str.data >= size) {
FLT_OT_ERR("sample data size too large");
}
else if (data->u.str.data > 0) {
retval = data->u.str.data;
memcpy(value, data->u.str.area, retval);
value[retval] = '\0';
}
else {
/*
* There is no content to add but we will still return
* the correct status.
*/
retval = 0;
}
}
else if (data->type == SMP_T_BIN) {
FLT_OT_ERR("invalid sample data type %d", data->type);
}
else if (data->type != SMP_T_METH) {
FLT_OT_ERR("invalid sample data type %d", data->type);
}
else if (data->u.meth.meth == HTTP_METH_OPTIONS) {
retval = FLT_OT_STR_SIZE(HTTP_METH_STR_OPTIONS);
(void)memcpy(value, HTTP_METH_STR_OPTIONS, retval + 1);
}
else if (data->u.meth.meth == HTTP_METH_GET) {
retval = FLT_OT_STR_SIZE(HTTP_METH_STR_GET);
(void)memcpy(value, HTTP_METH_STR_GET, retval + 1);
}
else if (data->u.meth.meth == HTTP_METH_HEAD) {
retval = FLT_OT_STR_SIZE(HTTP_METH_STR_HEAD);
(void)memcpy(value, HTTP_METH_STR_HEAD, retval + 1);
}
else if (data->u.meth.meth == HTTP_METH_POST) {
retval = FLT_OT_STR_SIZE(HTTP_METH_STR_POST);
(void)memcpy(value, HTTP_METH_STR_POST, retval + 1);
}
else if (data->u.meth.meth == HTTP_METH_PUT) {
retval = FLT_OT_STR_SIZE(HTTP_METH_STR_PUT);
(void)memcpy(value, HTTP_METH_STR_PUT, retval + 1);
}
else if (data->u.meth.meth == HTTP_METH_DELETE) {
retval = FLT_OT_STR_SIZE(HTTP_METH_STR_DELETE);
(void)memcpy(value, HTTP_METH_STR_DELETE, retval + 1);
}
else if (data->u.meth.meth == HTTP_METH_TRACE) {
retval = FLT_OT_STR_SIZE(HTTP_METH_STR_TRACE);
(void)memcpy(value, HTTP_METH_STR_TRACE, retval + 1);
}
else if (data->u.meth.meth == HTTP_METH_CONNECT) {
retval = FLT_OT_STR_SIZE(HTTP_METH_STR_CONNECT);
(void)memcpy(value, HTTP_METH_STR_CONNECT, retval + 1);
}
else if (data->u.meth.meth == HTTP_METH_OTHER) {
if (data->u.meth.str.data >= size) {
FLT_OT_ERR("sample data size too large");
} else {
retval = data->u.meth.str.data;
memcpy(value, data->u.meth.str.area, retval);
value[retval] = '\0';
}
}
else {
FLT_OT_ERR("invalid HTTP method");
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_sample_to_value -
*
* ARGUMENTS
* key -
* data -
* value -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int flt_ot_sample_to_value(const char *key, const struct sample_data *data, struct otc_value *value, char **err)
{
int retval = -1;
FLT_OT_FUNC("\"%s\", %p, %p, %p:%p", key, data, value, FLT_OT_DPTR_ARGS(err));
if ((data == NULL) || (value == NULL))
FLT_OT_RETURN_INT(retval);
if (data->type == SMP_T_BOOL) {
value->type = otc_value_bool;
value->value.bool_value = data->u.sint ? 1 : 0;
retval = sizeof(value->value.bool_value);
}
else if (data->type == SMP_T_SINT) {
value->type = otc_value_int64;
value->value.int64_value = data->u.sint;
retval = sizeof(value->value.int64_value);
}
else {
value->type = otc_value_string;
value->value.string_value = FLT_OT_MALLOC(global.tune.bufsize);
if (value->value.string_value == NULL)
FLT_OT_ERR("out of memory");
else
retval = flt_ot_sample_to_str(data, (char *)value->value.string_value, global.tune.bufsize, err);
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_sample_add -
*
* ARGUMENTS
* s -
* dir -
* sample -
* data -
* type -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
int flt_ot_sample_add(struct stream *s, uint dir, struct flt_ot_conf_sample *sample, struct flt_ot_scope_data *data, int type, char **err)
{
const struct flt_ot_conf_sample_expr *expr;
struct sample smp;
struct otc_value value;
struct buffer buffer;
int idx = 0, rc, retval = FLT_OT_RET_OK;
FLT_OT_FUNC("%p, %u, %p, %p, %d, %p:%p", s, dir, data, sample, type, FLT_OT_DPTR_ARGS(err));
FLT_OT_DBG_CONF_SAMPLE("sample ", sample);
(void)memset(&buffer, 0, sizeof(buffer));
list_for_each_entry(expr, &(sample->exprs), list) {
FLT_OT_DBG_CONF_SAMPLE_EXPR("sample expression ", expr);
(void)memset(&smp, 0, sizeof(smp));
/*
* If we have only one expression to process, then the data
* type that is the result of the expression is converted to
* an equivalent data type (if possible) that is written to
* the tracer.
*
* If conversion is not possible, or if we have multiple
* expressions to process, then the result is converted to
* a string and as such sent to the tracer.
*/
if (sample_process(s->be, s->sess, s, dir | SMP_OPT_FINAL, expr->expr, &smp) != NULL) {
FLT_OT_DBG(3, "data type %d: '%s'", smp.data.type, expr->value);
} else {
FLT_OT_DBG(2, "WARNING: failed to fetch '%s' value", expr->value);
/*
* In case the fetch failed, we will set the result
* (sample) to an empty static string.
*/
(void)memset(&(smp.data), 0, sizeof(smp.data));
smp.data.type = SMP_T_STR;
smp.data.u.str.area = "";
}
if ((sample->num_exprs == 1) && (type == FLT_OT_EVENT_SAMPLE_TAG)) {
if (flt_ot_sample_to_value(sample->key, &(smp.data), &value, err) == -1)
retval = FLT_OT_RET_ERROR;
} else {
if (buffer.area == NULL) {
chunk_init(&buffer, FLT_OT_CALLOC(1, global.tune.bufsize), global.tune.bufsize);
if (buffer.area == NULL) {
FLT_OT_ERR("out of memory");
retval = FLT_OT_RET_ERROR;
break;
}
}
rc = flt_ot_sample_to_str(&(smp.data), buffer.area + buffer.data, buffer.size - buffer.data, err);
if (rc == -1) {
retval = FLT_OT_RET_ERROR;
} else {
buffer.data += rc;
if (sample->num_exprs == ++idx) {
value.type = otc_value_string;
value.value.string_value = buffer.area;
}
}
}
}
if (retval == FLT_OT_RET_ERROR) {
/* Do nothing. */
}
else if (type == FLT_OT_EVENT_SAMPLE_TAG) {
struct otc_tag *tag = data->tags + data->num_tags++;
tag->key = sample->key;
(void)memcpy(&(tag->value), &value, sizeof(tag->value));
}
else if (type == FLT_OT_EVENT_SAMPLE_LOG) {
struct otc_log_field *log_field = data->log_fields + data->num_log_fields++;
log_field->key = sample->key;
(void)memcpy(&(log_field->value), &value, sizeof(log_field->value));
}
else {
if (data->baggage == NULL)
data->baggage = otc_text_map_new(NULL, FLT_OT_MAXBAGGAGES);
if (data->baggage == NULL) {
FLT_OT_ERR("out of memory");
retval = FLT_OT_RET_ERROR;
}
else if (otc_text_map_add(data->baggage, sample->key, 0, value.value.string_value, 0, 0) == -1) {
FLT_OT_ERR("out of memory");
retval = FLT_OT_RET_ERROR;
}
else
FLT_OT_DBG(3, "baggage[%zu]: '%s' -> '%s'", data->baggage->count - 1, data->baggage->key[data->baggage->count - 1], data->baggage->value[data->baggage->count - 1]);
}
FLT_OT_RETURN_INT(retval);
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,841 +0,0 @@
/***
* Copyright 2020 HAProxy Technologies
*
* This file is part of the HAProxy OpenTracing filter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "../include/include.h"
#ifdef DEBUG_OT
/***
* NAME
* flt_ot_vars_scope_dump -
*
* ARGUMENTS
* vars -
* scope -
*
* DESCRIPTION
* Function prints the contents of all variables defined for a particular
* scope.
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_ot_vars_scope_dump(struct vars *vars, const char *scope)
{
int i;
if (vars == NULL)
return;
vars_rdlock(vars);
for (i = 0; i < VAR_NAME_ROOTS; i++) {
struct ceb_node *node = cebu64_imm_first(&(vars->name_root[i]));
for ( ; node != NULL; node = cebu64_imm_next(&(vars->name_root[i]), node)) {
struct var *var = container_of(node, struct var, name_node);
FLT_OT_DBG(2, "'%s.%016" PRIx64 "' -> '%.*s'", scope, var->name_hash, (int)b_data(&(var->data.u.str)), b_orig(&(var->data.u.str)));
}
}
vars_rdunlock(vars);
}
/***
* NAME
* flt_ot_vars_dump -
*
* ARGUMENTS
* s -
*
* DESCRIPTION
* Function prints the contents of all variables grouped by individual
* scope.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_ot_vars_dump(struct stream *s)
{
FLT_OT_FUNC("%p", s);
/*
* It would be nice if we could use the get_vars() function from HAProxy
* source here to get the value of the 'vars' pointer, but it is defined
* as 'static inline', so unfortunately none of this is possible.
*/
flt_ot_vars_scope_dump(&(proc_vars), "PROC");
flt_ot_vars_scope_dump(&(s->sess->vars), "SESS");
flt_ot_vars_scope_dump(&(s->vars_txn), "TXN");
flt_ot_vars_scope_dump(&(s->vars_reqres), "REQ/RES");
FLT_OT_RETURN();
}
#endif /* DEBUG_OT */
/***
* NAME
* flt_ot_smp_init -
*
* ARGUMENTS
* s -
* smp -
* opt -
* type -
* data -
*
* DESCRIPTION
* The function initializes the value of the 'smp' structure. If the 'data'
* argument is set, then the 'sample_data' member of the 'smp' structure is
* also initialized.
*
* RETURN VALUE
* This function does not return a value.
*/
static inline void flt_ot_smp_init(struct stream *s, struct sample *smp, uint opt, int type, const char *data)
{
(void)memset(smp, 0, sizeof(*smp));
(void)smp_set_owner(smp, s->be, s->sess, s, opt | SMP_OPT_FINAL);
if (data != NULL) {
smp->data.type = type;
chunk_initstr(&(smp->data.u.str), data);
}
}
/***
* NAME
* flt_ot_smp_add -
*
* ARGUMENTS
* data -
* blk -
* len -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_smp_add(struct sample_data *data, const char *name, size_t len, char **err)
{
bool flag_alloc = 0;
int retval = FLT_OT_RET_ERROR;
FLT_OT_FUNC("%p, \"%.*s\", %zu, %p:%p", data, (int)len, name, len, FLT_OT_DPTR_ARGS(err));
FLT_OT_DBG_BUF(2, &(data->u.str));
if (b_orig(&(data->u.str)) == NULL) {
data->type = SMP_T_BIN;
chunk_init(&(data->u.str), FLT_OT_MALLOC(global.tune.bufsize), global.tune.bufsize);
flag_alloc = (b_orig(&(data->u.str)) != NULL);
}
if (b_orig(&(data->u.str)) == NULL) {
FLT_OT_ERR("failed to add ctx '%.*s', not enough memory", (int)len, name);
}
else if (len > ((UINT64_C(1) << ((sizeof(FLT_OT_VAR_CTX_SIZE) << 3) - 1)) - 1)) {
FLT_OT_ERR("failed to add ctx '%.*s', too long name", (int)len, name);
}
else if ((len + sizeof(FLT_OT_VAR_CTX_SIZE)) > b_room(&(data->u.str))) {
FLT_OT_ERR("failed to add ctx '%.*s', too many names", (int)len, name);
}
else {
retval = b_data(&(data->u.str));
b_putchr(&(data->u.str), len);
(void)__b_putblk(&(data->u.str), name, len);
FLT_OT_DBG_BUF(2, &(data->u.str));
}
if ((retval == FLT_OT_RET_ERROR) && flag_alloc)
FLT_OT_FREE(b_orig(&(data->u.str)));
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_normalize_name -
*
* ARGUMENTS
* var_name -
* size -
* len -
* name -
* flag_cpy -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_normalize_name(char *var_name, size_t size, int *len, const char *name, bool flag_cpy, char **err)
{
int retval = 0;
FLT_OT_FUNC("%p, %zu, %p, \"%s\", %hhu, %p:%p", var_name, size, len, name, flag_cpy, FLT_OT_DPTR_ARGS(err));
if (!FLT_OT_STR_ISVALID(name))
FLT_OT_RETURN_INT(retval);
/*
* In case the name of the variable consists of several elements,
* the character '.' is added between them.
*/
if ((*len == 0) || (var_name[*len - 1] == '.'))
/* Do nothing. */;
else if (*len < (size - 1))
var_name[(*len)++] = '.';
else {
FLT_OT_ERR("failed to normalize variable name, buffer too small");
retval = -1;
}
if (flag_cpy) {
/* Copy variable name without modification. */
retval = strlen(name);
if ((*len + retval + 1) > size) {
FLT_OT_ERR("failed to normalize variable name, buffer too small");
retval = -1;
} else {
(void)memcpy(var_name + *len, name, retval + 1);
*len += retval;
}
} else {
/*
* HAProxy does not allow the use of variable names containing '-'
* or ' '. This of course applies to HTTP header names as well.
* Also, here the capital letters are converted to lowercase.
*/
while (retval != -1)
if (*len >= (size - 1)) {
FLT_OT_ERR("failed to normalize variable name, buffer too small");
retval = -1;
} else {
uint8_t ch = name[retval];
if (ch == '\0')
break;
else if (ch == '-')
ch = FLT_OT_VAR_CHAR_DASH;
else if (ch == ' ')
ch = FLT_OT_VAR_CHAR_SPACE;
else if (isupper(ch))
ch = ist_lc[ch];
var_name[(*len)++] = ch;
retval++;
}
var_name[*len] = '\0';
}
FLT_OT_DBG(3, "var_name: \"%s\" %d/%d", var_name, retval, *len);
if (retval == -1)
*len = retval;
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_var_name -
*
* ARGUMENTS
* scope -
* prefix -
* name -
* flag_cpy -
* var_name -
* size -
* err -
*
* DESCRIPTION
* The function initializes the value of the 'smp' structure. If the 'data'
* argument is set, then the 'sample_data' member of the 'smp' structure is
* also initialized.
*
* RETURN VALUE
* -
*/
static int flt_ot_var_name(const char *scope, const char *prefix, const char *name, bool flag_cpy, char *var_name, size_t size, char **err)
{
int retval = 0;
FLT_OT_FUNC("\"%s\", \"%s\", \"%s\", %hhu, %p, %zu, %p:%p", scope, prefix, name, flag_cpy, var_name, size, FLT_OT_DPTR_ARGS(err));
if (flt_ot_normalize_name(var_name, size, &retval, scope, 0, err) >= 0)
if (flt_ot_normalize_name(var_name, size, &retval, prefix, 0, err) >= 0)
(void)flt_ot_normalize_name(var_name, size, &retval, name, flag_cpy, err);
if (retval == -1)
FLT_OT_ERR("failed to construct variable name '%s.%s.%s'", scope, prefix, name);
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_ctx_loop -
*
* ARGUMENTS
* smp -
* scope -
* prefix -
* err -
* func -
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_ctx_loop(struct sample *smp, const char *scope, const char *prefix, char **err, flt_ot_ctx_loop_cb func, void *ptr)
{
FLT_OT_VAR_CTX_SIZE var_ctx_size;
char var_name[BUFSIZ], var_ctx[BUFSIZ];
int i, var_name_len, var_ctx_len, rc, n = 1, retval = 0;
FLT_OT_FUNC("%p, \"%s\", \"%s\", %p:%p, %p, %p", smp, scope, prefix, FLT_OT_DPTR_ARGS(err), func, ptr);
/*
* The variable in which we will save the name of the OpenTracing
* context variable.
*/
var_name_len = flt_ot_var_name(scope, prefix, NULL, 0, var_name, sizeof(var_name), err);
if (var_name_len == -1)
FLT_OT_RETURN_INT(FLT_OT_RET_ERROR);
/*
* Here we will try to find all the previously recorded variables from
* the currently set OpenTracing context. If we find the required
* variable and it is marked as deleted, we will mark it as active.
* If we do not find it, then it is added to the end of the previously
* saved names.
*/
if (vars_get_by_name(var_name, var_name_len, smp, NULL) == 0) {
FLT_OT_DBG(2, "ctx '%s' no variable found", var_name);
}
else if (smp->data.type != SMP_T_BIN) {
FLT_OT_ERR("ctx '%s' invalid data type %d", var_name, smp->data.type);
retval = FLT_OT_RET_ERROR;
}
else {
FLT_OT_DBG_BUF(2, &(smp->data.u.str));
for (i = 0; i < b_data(&(smp->data.u.str)); i += sizeof(var_ctx_size) + var_ctx_len, n++) {
var_ctx_size = *((typeof(var_ctx_size) *)(b_orig(&(smp->data.u.str)) + i));
var_ctx_len = abs(var_ctx_size);
if ((i + sizeof(var_ctx_size) + var_ctx_len) > b_data(&(smp->data.u.str))) {
FLT_OT_ERR("ctx '%s' invalid data size", var_name);
retval = FLT_OT_RET_ERROR;
break;
}
(void)memcpy(var_ctx, b_orig(&(smp->data.u.str)) + i + sizeof(var_ctx_size), var_ctx_len);
var_ctx[var_ctx_len] = '\0';
rc = func(smp, i, scope, prefix, var_ctx, var_ctx_size, err, ptr);
if (rc == FLT_OT_RET_ERROR) {
retval = FLT_OT_RET_ERROR;
break;
}
else if (rc > 0) {
retval = n;
break;
}
}
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_ctx_set_cb -
*
* ARGUMENTS
* smp -
* idx -
* scope -
* prefix -
* name -
* name_len -
* err -
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_ctx_set_cb(struct sample *smp, size_t idx, const char *scope, const char *prefix, const char *name, FLT_OT_VAR_CTX_SIZE name_len, char **err, void *ptr)
{
struct flt_ot_ctx *ctx = ptr;
int retval = 0;
FLT_OT_FUNC("%p, %zu, \"%s\", \"%s\", \"%s\", %hhd, %p:%p, %p", smp, idx, scope, prefix, name, name_len, FLT_OT_DPTR_ARGS(err), ptr);
if ((name_len == ctx->value_len) && (strncmp(name, ctx->value, name_len) == 0)) {
FLT_OT_DBG(2, "ctx '%s' found\n", name);
retval = 1;
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_ctx_set -
*
* ARGUMENTS
* s -
* scope -
* prefix -
* name -
* opt -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_ctx_set(struct stream *s, const char *scope, const char *prefix, const char *name, uint opt, char **err)
{
struct flt_ot_ctx ctx;
struct sample smp_ctx;
char var_name[BUFSIZ];
bool flag_alloc = 0;
int rc, var_name_len, retval = FLT_OT_RET_ERROR;
FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, name, opt, FLT_OT_DPTR_ARGS(err));
/*
* The variable in which we will save the name of the OpenTracing
* context variable.
*/
var_name_len = flt_ot_var_name(scope, prefix, NULL, 0, var_name, sizeof(var_name), err);
if (var_name_len == -1)
FLT_OT_RETURN_INT(retval);
/* Normalized name of the OpenTracing context variable. */
ctx.value_len = flt_ot_var_name(name, NULL, NULL, 0, ctx.value, sizeof(ctx.value), err);
if (ctx.value_len == -1)
FLT_OT_RETURN_INT(retval);
flt_ot_smp_init(s, &smp_ctx, opt, 0, NULL);
retval = flt_ot_ctx_loop(&smp_ctx, scope, prefix, err, flt_ot_ctx_set_cb, &ctx);
if (retval == 0) {
rc = flt_ot_smp_add(&(smp_ctx.data), ctx.value, ctx.value_len, err);
if (rc == FLT_OT_RET_ERROR)
retval = FLT_OT_RET_ERROR;
flag_alloc = (rc == 0);
}
if (retval == FLT_OT_RET_ERROR) {
/* Do nothing. */
}
else if (retval > 0) {
FLT_OT_DBG(2, "ctx '%s' data found", ctx.value);
}
else if (vars_set_by_name_ifexist(var_name, var_name_len, &smp_ctx) == 0) {
FLT_OT_ERR("failed to set ctx '%s'", var_name);
retval = FLT_OT_RET_ERROR;
}
else {
FLT_OT_DBG(2, "ctx '%s' -> '%.*s' set", var_name, (int)b_data(&(smp_ctx.data.u.str)), b_orig(&(smp_ctx.data.u.str)));
retval = b_data(&(smp_ctx.data.u.str));
}
if (flag_alloc)
FLT_OT_FREE(b_orig(&(smp_ctx.data.u.str)));
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_var_register -
*
* ARGUMENTS
* scope -
* prefix -
* name -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int flt_ot_var_register(const char *scope, const char *prefix, const char *name, char **err)
{
struct arg arg;
char var_name[BUFSIZ];
int retval = -1, var_name_len;
FLT_OT_FUNC("\"%s\", \"%s\", \"%s\", %p:%p", scope, prefix, name, FLT_OT_DPTR_ARGS(err));
var_name_len = flt_ot_var_name(scope, prefix, name, 0, var_name, sizeof(var_name), err);
if (var_name_len == -1)
FLT_OT_RETURN_INT(retval);
/* Set <size> to 0 to not release var_name memory in vars_check_arg(). */
(void)memset(&arg, 0, sizeof(arg));
arg.type = ARGT_STR;
arg.data.str.area = var_name;
arg.data.str.data = var_name_len;
if (vars_check_arg(&arg, err) == 0) {
FLT_OT_ERR_APPEND("failed to register variable '%s': %s", var_name, *err);
} else {
FLT_OT_DBG(2, "variable '%s' registered", var_name);
retval = var_name_len;
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_var_set -
*
* ARGUMENTS
* s -
* scope -
* prefix -
* name -
* value -
* opt -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int flt_ot_var_set(struct stream *s, const char *scope, const char *prefix, const char *name, const char *value, uint opt, char **err)
{
struct sample smp;
char var_name[BUFSIZ];
int retval = -1, var_name_len;
FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, name, value, opt, FLT_OT_DPTR_ARGS(err));
var_name_len = flt_ot_var_name(scope, prefix, name, 0, var_name, sizeof(var_name), err);
if (var_name_len == -1)
FLT_OT_RETURN_INT(retval);
flt_ot_smp_init(s, &smp, opt, SMP_T_STR, value);
if (vars_set_by_name_ifexist(var_name, var_name_len, &smp) == 0) {
FLT_OT_ERR("failed to set variable '%s'", var_name);
} else {
FLT_OT_DBG(2, "variable '%s' set", var_name);
retval = var_name_len;
if (strcmp(scope, FLT_OT_VARS_SCOPE) == 0)
retval = flt_ot_ctx_set(s, scope, prefix, name, opt, err);
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_vars_unset_cb -
*
* ARGUMENTS
* smp -
* idx -
* scope -
* prefix -
* name -
* name_len -
* err -
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_vars_unset_cb(struct sample *smp, size_t idx, const char *scope, const char *prefix, const char *name, FLT_OT_VAR_CTX_SIZE name_len, char **err, void *ptr)
{
struct sample smp_ctx;
char var_ctx[BUFSIZ];
int var_ctx_len, retval = FLT_OT_RET_ERROR;
FLT_OT_FUNC("%p, %zu, \"%s\", \"%s\", \"%s\", %hhd, %p:%p, %p", smp, idx, scope, prefix, name, name_len, FLT_OT_DPTR_ARGS(err), ptr);
var_ctx_len = flt_ot_var_name(scope, prefix, name, 1, var_ctx, sizeof(var_ctx), err);
if (var_ctx_len == -1) {
FLT_OT_ERR("ctx '%s' invalid", name);
FLT_OT_RETURN_INT(retval);
}
flt_ot_smp_init(smp->strm, &smp_ctx, smp->opt, 0, NULL);
if (vars_unset_by_name_ifexist(var_ctx, var_ctx_len, &smp_ctx) == 0) {
FLT_OT_ERR("ctx '%s' no variable found", var_ctx);
} else {
FLT_OT_DBG(2, "ctx '%s' unset", var_ctx);
retval = 0;
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_vars_unset -
*
* ARGUMENTS
* s -
* scope -
* prefix -
* opt -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
int flt_ot_vars_unset(struct stream *s, const char *scope, const char *prefix, uint opt, char **err)
{
struct sample smp_ctx;
char var_name[BUFSIZ];
int var_name_len, retval;
FLT_OT_FUNC("%p, \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, opt, FLT_OT_DPTR_ARGS(err));
flt_ot_smp_init(s, &smp_ctx, opt, 0, NULL);
retval = flt_ot_ctx_loop(&smp_ctx, scope, prefix, err, flt_ot_vars_unset_cb, NULL);
if (retval != FLT_OT_RET_ERROR) {
/*
* After all ctx variables have been unset, the variable used
* to store their names should also be unset.
*/
var_name_len = flt_ot_var_name(scope, prefix, NULL, 0, var_name, sizeof(var_name), err);
if (var_name_len == -1)
FLT_OT_RETURN_INT(FLT_OT_RET_ERROR);
flt_ot_smp_init(s, &smp_ctx, opt, 0, NULL);
if (vars_unset_by_name_ifexist(var_name, var_name_len, &smp_ctx) == 0) {
FLT_OT_DBG(2, "variable '%s' not found", var_name);
} else {
FLT_OT_DBG(2, "variable '%s' unset", var_name);
retval = 1;
}
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_vars_get_cb -
*
* ARGUMENTS
* smp -
* idx -
* scope -
* prefix -
* name -
* name_len -
* err -
* ptr -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
static int flt_ot_vars_get_cb(struct sample *smp, size_t idx, const char *scope, const char *prefix, const char *name, FLT_OT_VAR_CTX_SIZE name_len, char **err, void *ptr)
{
struct otc_text_map **map = ptr;
struct sample smp_ctx;
char var_ctx[BUFSIZ], ot_var_name[BUFSIZ], ch;
int var_ctx_len, ot_var_name_len, retval = FLT_OT_RET_ERROR;
FLT_OT_FUNC("%p, %zu, \"%s\", \"%s\", \"%s\", %hhd, %p:%p, %p", smp, idx, scope, prefix, name, name_len, FLT_OT_DPTR_ARGS(err), ptr);
var_ctx_len = flt_ot_var_name(scope, prefix, name, 1, var_ctx, sizeof(var_ctx), err);
if (var_ctx_len == -1) {
FLT_OT_ERR("ctx '%s' invalid", name);
FLT_OT_RETURN_INT(retval);
}
flt_ot_smp_init(smp->strm, &smp_ctx, smp->opt, 0, NULL);
if (vars_get_by_name(var_ctx, var_ctx_len, &smp_ctx, NULL) != 0) {
FLT_OT_DBG(2, "'%s' -> '%.*s'", var_ctx, (int)b_data(&(smp_ctx.data.u.str)), b_orig(&(smp_ctx.data.u.str)));
if (*map == NULL) {
*map = otc_text_map_new(NULL, 8);
if (*map == NULL) {
FLT_OT_ERR("failed to create map data");
FLT_OT_RETURN_INT(FLT_OT_RET_ERROR);
}
}
/*
* Eh, because the use of some characters is not allowed
* in the variable name, the conversion of the replaced
* characters to the original is performed here.
*/
for (ot_var_name_len = 0; (ch = name[ot_var_name_len]) != '\0'; ot_var_name_len++)
if (ot_var_name_len >= (FLT_OT_TABLESIZE(ot_var_name) - 1)) {
FLT_OT_ERR("failed to reverse variable name, buffer too small");
otc_text_map_destroy(map, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
break;
} else {
ot_var_name[ot_var_name_len] = (ch == FLT_OT_VAR_CHAR_DASH) ? '-' : ((ch == FLT_OT_VAR_CHAR_SPACE) ? ' ' : ch);
}
ot_var_name[ot_var_name_len] = '\0';
if (*map == NULL) {
retval = FLT_OT_RET_ERROR;
}
else if (otc_text_map_add(*map, ot_var_name, ot_var_name_len, b_orig(&(smp_ctx.data.u.str)), b_data(&(smp_ctx.data.u.str)), OTC_TEXT_MAP_DUP_KEY | OTC_TEXT_MAP_DUP_VALUE) == -1) {
FLT_OT_ERR("failed to add map data");
otc_text_map_destroy(map, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
retval = FLT_OT_RET_ERROR;
}
else {
retval = 0;
}
} else {
FLT_OT_DBG(2, "ctx '%s' no variable found", var_ctx);
}
FLT_OT_RETURN_INT(retval);
}
/***
* NAME
* flt_ot_vars_get -
*
* ARGUMENTS
* s -
* scope -
* prefix -
* opt -
* err -
*
* DESCRIPTION
* -
*
* RETURN VALUE
* -
*/
struct otc_text_map *flt_ot_vars_get(struct stream *s, const char *scope, const char *prefix, uint opt, char **err)
{
struct sample smp_ctx;
struct otc_text_map *retptr = NULL;
FLT_OT_FUNC("%p, \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, opt, FLT_OT_DPTR_ARGS(err));
flt_ot_smp_init(s, &smp_ctx, opt, 0, NULL);
(void)flt_ot_ctx_loop(&smp_ctx, scope, prefix, err, flt_ot_vars_get_cb, &retptr);
ot_text_map_show(retptr);
if ((retptr != NULL) && (retptr->count == 0)) {
FLT_OT_DBG(2, "WARNING: no variables found");
otc_text_map_destroy(&retptr, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
}
FLT_OT_RETURN_PTR(retptr);
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,111 +0,0 @@
--- rate-limit 100.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 650.95us 431.15us 46.44ms 96.67%
Req/Sec 1.44k 51.39 2.57k 74.89%
Latency Distribution
50% 608.00us
75% 760.00us
90% 0.91ms
99% 1.31ms
3434836 requests in 5.00m, 0.89GB read
Requests/sec: 11446.99
Transfer/sec: 3.03MB
----------------------------------------------------------------------
--- rate-limit 50.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 398.00us 371.39us 22.56ms 97.23%
Req/Sec 2.32k 84.01 2.76k 74.84%
Latency Distribution
50% 350.00us
75% 467.00us
90% 593.00us
99% 1.03ms
5530848 requests in 5.00m, 1.43GB read
Requests/sec: 18434.31
Transfer/sec: 4.89MB
----------------------------------------------------------------------
--- rate-limit 10.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 316.75us 351.92us 23.00ms 98.57%
Req/Sec 2.87k 94.02 3.22k 79.30%
Latency Distribution
50% 273.00us
75% 342.00us
90% 424.00us
99% 0.94ms
6859293 requests in 5.00m, 1.78GB read
Requests/sec: 22862.16
Transfer/sec: 6.06MB
----------------------------------------------------------------------
--- rate-limit 2.5 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 307.90us 368.64us 26.08ms 98.71%
Req/Sec 2.96k 103.84 3.23k 83.76%
Latency Distribution
50% 264.00us
75% 327.00us
90% 402.00us
99% 0.97ms
7065667 requests in 5.00m, 1.83GB read
Requests/sec: 23550.37
Transfer/sec: 6.24MB
----------------------------------------------------------------------
--- rate-limit 0.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 304.60us 376.36us 30.26ms 98.74%
Req/Sec 2.99k 106.93 3.24k 83.08%
Latency Distribution
50% 262.00us
75% 323.00us
90% 396.00us
99% 0.95ms
7136261 requests in 5.00m, 1.85GB read
Requests/sec: 23785.77
Transfer/sec: 6.31MB
----------------------------------------------------------------------
--- rate-limit disabled --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 300.90us 342.35us 22.13ms 98.74%
Req/Sec 3.00k 95.67 3.33k 81.11%
Latency Distribution
50% 261.00us
75% 322.00us
90% 394.00us
99% 806.00us
7159525 requests in 5.00m, 1.85GB read
Requests/sec: 23863.05
Transfer/sec: 6.33MB
----------------------------------------------------------------------
--- rate-limit off --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 302.51us 371.99us 30.26ms 98.77%
Req/Sec 3.00k 104.43 3.73k 83.74%
Latency Distribution
50% 260.00us
75% 321.00us
90% 394.00us
99% 0.89ms
7170345 requests in 5.00m, 1.86GB read
Requests/sec: 23898.19
Transfer/sec: 6.34MB
----------------------------------------------------------------------

View file

@ -1,111 +0,0 @@
--- rate-limit 100.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.49ms 799.87us 43.00ms 70.90%
Req/Sec 393.01 20.61 696.00 71.68%
Latency Distribution
50% 2.50ms
75% 3.00ms
90% 3.38ms
99% 4.23ms
939237 requests in 5.00m, 249.01MB read
Requests/sec: 3130.01
Transfer/sec: 849.75KB
----------------------------------------------------------------------
--- rate-limit 50.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.27ms 0.97ms 40.77ms 56.91%
Req/Sec 778.22 70.30 1.36k 69.10%
Latency Distribution
50% 1.36ms
75% 1.80ms
90% 2.49ms
99% 3.51ms
1859055 requests in 5.00m, 492.88MB read
Requests/sec: 6195.58
Transfer/sec: 1.64MB
----------------------------------------------------------------------
--- rate-limit 10.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 442.00us 481.47us 31.61ms 90.27%
Req/Sec 2.25k 130.05 2.73k 72.83%
Latency Distribution
50% 287.00us
75% 526.00us
90% 0.92ms
99% 1.76ms
5380213 requests in 5.00m, 1.39GB read
Requests/sec: 17930.27
Transfer/sec: 4.75MB
----------------------------------------------------------------------
--- rate-limit 2.5 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 346.65us 414.65us 28.50ms 95.63%
Req/Sec 2.75k 159.74 3.23k 84.68%
Latency Distribution
50% 271.00us
75% 353.00us
90% 505.00us
99% 1.55ms
6560093 requests in 5.00m, 1.70GB read
Requests/sec: 21864.43
Transfer/sec: 5.80MB
----------------------------------------------------------------------
--- rate-limit 0.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 313.32us 402.25us 24.73ms 98.55%
Req/Sec 2.95k 145.03 3.21k 88.99%
Latency Distribution
50% 264.00us
75% 327.00us
90% 403.00us
99% 1.33ms
7050847 requests in 5.00m, 1.83GB read
Requests/sec: 23501.14
Transfer/sec: 6.23MB
----------------------------------------------------------------------
--- rate-limit disabled --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 310.19us 384.76us 22.18ms 98.66%
Req/Sec 2.96k 115.62 3.37k 84.30%
Latency Distribution
50% 265.00us
75% 327.00us
90% 402.00us
99% 1.10ms
7058682 requests in 5.00m, 1.83GB read
Requests/sec: 23526.70
Transfer/sec: 6.24MB
----------------------------------------------------------------------
--- rate-limit off --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 305.86us 367.56us 25.76ms 98.65%
Req/Sec 2.99k 116.93 3.43k 85.59%
Latency Distribution
50% 261.00us
75% 322.00us
90% 396.00us
99% 1.09ms
7137173 requests in 5.00m, 1.85GB read
Requests/sec: 23788.84
Transfer/sec: 6.31MB
----------------------------------------------------------------------

View file

@ -1,111 +0,0 @@
--- rate-limit 100.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 0.89ms 466.84us 35.44ms 94.39%
Req/Sec 1.09k 39.30 1.32k 72.60%
Latency Distribution
50% 823.00us
75% 1.00ms
90% 1.20ms
99% 2.14ms
2594524 requests in 5.00m, 687.86MB read
Requests/sec: 8645.83
Transfer/sec: 2.29MB
----------------------------------------------------------------------
--- rate-limit 50.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 681.74us 463.28us 20.45ms 95.46%
Req/Sec 1.41k 54.00 1.60k 68.97%
Latency Distribution
50% 613.00us
75% 785.00us
90% 0.98ms
99% 2.06ms
3367473 requests in 5.00m, 0.87GB read
Requests/sec: 11222.76
Transfer/sec: 2.98MB
----------------------------------------------------------------------
--- rate-limit 10.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 558.32us 458.54us 29.40ms 97.73%
Req/Sec 1.72k 60.67 2.05k 73.10%
Latency Distribution
50% 494.00us
75% 610.00us
90% 743.00us
99% 2.08ms
4105420 requests in 5.00m, 1.06GB read
Requests/sec: 13683.36
Transfer/sec: 3.63MB
----------------------------------------------------------------------
--- rate-limit 2.5 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 542.66us 440.31us 22.63ms 97.88%
Req/Sec 1.76k 60.02 2.00k 72.27%
Latency Distribution
50% 481.00us
75% 588.00us
90% 710.00us
99% 2.05ms
4214525 requests in 5.00m, 1.09GB read
Requests/sec: 14046.76
Transfer/sec: 3.72MB
----------------------------------------------------------------------
--- rate-limit 0.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 529.06us 414.38us 30.09ms 97.97%
Req/Sec 1.80k 59.34 2.05k 74.47%
Latency Distribution
50% 473.00us
75% 576.00us
90% 692.00us
99% 1.79ms
4287428 requests in 5.00m, 1.11GB read
Requests/sec: 14290.45
Transfer/sec: 3.79MB
----------------------------------------------------------------------
--- rate-limit disabled --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 517.81us 463.10us 36.81ms 98.25%
Req/Sec 1.85k 62.39 2.21k 75.65%
Latency Distribution
50% 458.00us
75% 558.00us
90% 670.00us
99% 1.96ms
4416273 requests in 5.00m, 1.14GB read
Requests/sec: 14719.43
Transfer/sec: 3.90MB
----------------------------------------------------------------------
--- rate-limit off --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 511.67us 428.18us 27.68ms 98.15%
Req/Sec 1.86k 60.67 2.05k 75.44%
Latency Distribution
50% 455.00us
75% 554.00us
90% 666.00us
99% 1.81ms
4441271 requests in 5.00m, 1.15GB read
Requests/sec: 14803.32
Transfer/sec: 3.92MB
----------------------------------------------------------------------

View file

@ -1,111 +0,0 @@
--- rate-limit 100.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.24ms 522.78us 35.59ms 79.12%
Req/Sec 767.71 38.72 3.02k 72.19%
Latency Distribution
50% 1.20ms
75% 1.51ms
90% 1.78ms
99% 2.37ms
1834067 requests in 5.00m, 486.25MB read
Requests/sec: 6111.57
Transfer/sec: 1.62MB
----------------------------------------------------------------------
--- rate-limit 50.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 593.11us 476.81us 43.00ms 91.27%
Req/Sec 1.59k 81.15 2.07k 71.14%
Latency Distribution
50% 549.00us
75% 788.00us
90% 1.03ms
99% 1.62ms
3795987 requests in 5.00m, 0.98GB read
Requests/sec: 12650.65
Transfer/sec: 3.35MB
----------------------------------------------------------------------
--- rate-limit 10.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 326.02us 355.00us 29.23ms 98.05%
Req/Sec 2.80k 88.05 3.30k 75.36%
Latency Distribution
50% 277.00us
75% 356.00us
90% 456.00us
99% 0.97ms
6675563 requests in 5.00m, 1.73GB read
Requests/sec: 22249.78
Transfer/sec: 5.90MB
----------------------------------------------------------------------
--- rate-limit 2.5 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 311.77us 357.45us 24.11ms 98.62%
Req/Sec 2.91k 94.70 3.18k 78.52%
Latency Distribution
50% 268.00us
75% 334.00us
90% 413.00us
99% 0.94ms
6960933 requests in 5.00m, 1.80GB read
Requests/sec: 23201.07
Transfer/sec: 6.15MB
----------------------------------------------------------------------
--- rate-limit 0.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 302.51us 330.50us 25.84ms 98.69%
Req/Sec 2.98k 91.46 3.40k 78.84%
Latency Distribution
50% 263.00us
75% 325.00us
90% 397.00us
99% 812.00us
7112084 requests in 5.00m, 1.84GB read
Requests/sec: 23705.14
Transfer/sec: 6.28MB
----------------------------------------------------------------------
--- rate-limit disabled --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 303.01us 353.98us 28.03ms 98.76%
Req/Sec 2.99k 93.97 3.34k 81.12%
Latency Distribution
50% 262.00us
75% 323.00us
90% 395.00us
99% 838.00us
7133837 requests in 5.00m, 1.85GB read
Requests/sec: 23777.95
Transfer/sec: 6.30MB
----------------------------------------------------------------------
--- rate-limit off --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 302.61us 349.74us 25.48ms 98.75%
Req/Sec 2.99k 94.85 3.49k 80.75%
Latency Distribution
50% 262.00us
75% 323.00us
90% 395.00us
99% 822.00us
7132714 requests in 5.00m, 1.85GB read
Requests/sec: 23773.35
Transfer/sec: 6.30MB
----------------------------------------------------------------------

View file

@ -1,5 +0,0 @@
{
"service": "BE",
"agent_host": "localhost",
"agent_port": 8126
}

View file

@ -1,34 +0,0 @@
service_name:
BE
###
# When using configuration object to instantiate the tracer, the type of
# sampling can be selected via sampler.type and sampler.param properties.
# Jaeger libraries support the following samplers:
#
# - Constant (sampler.type=const) sampler always makes the same decision for
# all traces. It either samples all traces (sampler.param=1) or none of
# them (sampler.param=0).
#
# - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
# decision with the probability of sampling equal to the value of
# sampler.param property. For example, with sampler.param=0.1 approximately
# 1 in 10 traces will be sampled.
#
# - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
# limiter to ensure that traces are sampled with a certain constant rate.
# For example, when sampler.param=2.0 it will sample requests with the rate
# of 2 traces per second.
#
# - Remote (sampler.type=remote, which is also the default) sampler consults
# Jaeger agent for the appropriate sampling strategy to use in the current
# service. This allows controlling the sampling strategies in the services
# from a central configuration in Jaeger backend, or even dynamically.
#
sampler:
type: ratelimiting
param: 10.0
reporter:
logSpans: true
localAgentHostPort: localhost:6831

View file

@ -1,4 +0,0 @@
{
"service_name": "BE",
"collector_host": "localhost"
}

View file

@ -1,37 +0,0 @@
global
# nbthread 1
maxconn 5000
hard-stop-after 10s
# log localhost:514 local7 debug
# debug
stats socket /tmp/haproxy-be.sock mode 666 level admin
defaults
log global
mode http
option httplog
option dontlognull
option httpclose
retries 3
maxconn 4000
timeout connect 5000
timeout client 50000
timeout server 50000
listen stats
mode http
bind *:8002
stats uri /
stats admin if TRUE
stats refresh 10s
frontend ot-test-be-frontend
bind *:11080
mode http
default_backend servers-backend
filter opentracing id ot-test-be config be/ot.cfg
backend servers-backend
mode http
server server-1 127.0.0.1:8000

View file

@ -1,62 +0,0 @@
[ot-test-be]
ot-tracer ot-test-tracer
config be/cfg-jaeger.yml
plugin libjaeger_opentracing_plugin-0.5.0.so
# log localhost:514 local7 debug
option dontlog-normal
option hard-errors
no option disabled
scopes frontend_http_request
scopes backend_tcp_request
scopes backend_http_request
scopes client_session_end
scopes server_session_start
scopes tcp_response
scopes http_response
scopes server_session_end
ot-scope frontend_http_request
extract "ot-ctx" use-headers
span "HAProxy session" child-of "ot-ctx" root
baggage "haproxy_id" var(sess.ot.uuid)
span "Client session" child-of "HAProxy session"
span "Frontend HTTP request" child-of "Client session"
tag "http.method" method
tag "http.url" url
tag "http.version" str("HTTP/") req.ver
event on-frontend-http-request
ot-scope backend_tcp_request
span "Backend TCP request" follows-from "Frontend HTTP request"
finish "Frontend HTTP request"
event on-backend-tcp-request
ot-scope backend_http_request
span "Backend HTTP request" follows-from "Backend TCP request"
finish "Backend TCP request"
event on-backend-http-request
ot-scope client_session_end
finish "Client session"
event on-client-session-end
ot-scope server_session_start
span "Server session" child-of "HAProxy session"
finish "Backend HTTP request"
event on-server-session-start
ot-scope tcp_response
span "TCP response" child-of "Server session"
event on-tcp-response
ot-scope http_response
span "HTTP response" follows-from "TCP response"
tag "http.status_code" status
finish "TCP response"
event on-http-response
ot-scope server_session_end
finish *
event on-server-session-end

View file

@ -1,5 +0,0 @@
{
"service": "CMP",
"agent_host": "localhost",
"agent_port": 8126
}

View file

@ -1,34 +0,0 @@
service_name:
CMP
###
# When using configuration object to instantiate the tracer, the type of
# sampling can be selected via sampler.type and sampler.param properties.
# Jaeger libraries support the following samplers:
#
# - Constant (sampler.type=const) sampler always makes the same decision for
# all traces. It either samples all traces (sampler.param=1) or none of
# them (sampler.param=0).
#
# - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
# decision with the probability of sampling equal to the value of
# sampler.param property. For example, with sampler.param=0.1 approximately
# 1 in 10 traces will be sampled.
#
# - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
# limiter to ensure that traces are sampled with a certain constant rate.
# For example, when sampler.param=2.0 it will sample requests with the rate
# of 2 traces per second.
#
# - Remote (sampler.type=remote, which is also the default) sampler consults
# Jaeger agent for the appropriate sampling strategy to use in the current
# service. This allows controlling the sampling strategies in the services
# from a central configuration in Jaeger backend, or even dynamically.
#
sampler:
type: ratelimiting
param: 10.0
reporter:
logSpans: true
localAgentHostPort: localhost:6831

View file

@ -1,4 +0,0 @@
{
"service_name": "CMP",
"collector_host": "localhost"
}

View file

@ -1,36 +0,0 @@
global
# nbthread 1
maxconn 5000
hard-stop-after 10s
stats socket /tmp/haproxy.sock mode 666 level admin
defaults
log global
mode http
option httplog
option dontlognull
option httpclose
retries 3
maxconn 4000
timeout connect 5000
timeout client 50000
timeout server 50000
listen stats
mode http
bind *:8001
stats uri /
stats admin if TRUE
stats refresh 10s
frontend ot-test-cmp-frontend
bind *:10080
mode http
default_backend servers-backend
acl acl-http-status-ok status 100:399
filter opentracing id ot-test-cmp config cmp/ot.cfg
backend servers-backend
mode http
server server-1 127.0.0.1:8000

View file

@ -1,83 +0,0 @@
[ot-test-cmp]
ot-tracer ot-test-tracer
config cmp/cfg-jaeger.yml
plugin libjaeger_opentracing_plugin-0.5.0.so
# log localhost:514 local7 debug
option dontlog-normal
option hard-errors
no option disabled
rate-limit 100.0
scopes client_session_start
scopes frontend_tcp_request
scopes frontend_http_request
scopes backend_tcp_request
scopes backend_http_request
scopes server_unavailable
scopes server_session_start
scopes tcp_response
scopes http_response http_response-error server_session_end client_session_end
ot-scope client_session_start
span "HAProxy session" root
baggage "haproxy_id" var(sess.ot.uuid)
span "Client session" child-of "HAProxy session"
event on-client-session-start
ot-scope frontend_tcp_request
span "Frontend TCP request" child-of "Client session"
event on-frontend-tcp-request
ot-scope frontend_http_request
span "Frontend HTTP request" follows-from "Frontend TCP request"
tag "http.method" method
tag "http.url" url
tag "http.version" str("HTTP/") req.ver
finish "Frontend TCP request"
event on-frontend-http-request
ot-scope backend_tcp_request
span "Backend TCP request" follows-from "Frontend HTTP request"
finish "Frontend HTTP request"
event on-backend-tcp-request
ot-scope backend_http_request
span "Backend HTTP request" follows-from "Backend TCP request"
finish "Backend TCP request"
event on-backend-http-request
ot-scope server_unavailable
span "HAProxy session"
tag "error" bool(true)
log "status" str("503 Service Unavailable")
finish *
event on-server-unavailable
ot-scope server_session_start
span "Server session" child-of "HAProxy session"
finish "Backend HTTP request"
event on-server-session-start
ot-scope tcp_response
span "TCP response" child-of "Server session"
event on-tcp-response
ot-scope http_response
span "HTTP response" follows-from "TCP response"
tag "http.status_code" status
finish "TCP response"
event on-http-response
ot-scope http_response-error
span "HTTP response"
tag "error" bool(true)
event on-http-response if !acl-http-status-ok
ot-scope server_session_end
finish "HTTP response" "Server session"
event on-http-response
ot-scope client_session_end
finish "*"
event on-http-response

View file

@ -1,5 +0,0 @@
{
"service": "CTX",
"agent_host": "localhost",
"agent_port": 8126
}

View file

@ -1,34 +0,0 @@
service_name:
CTX
###
# When using configuration object to instantiate the tracer, the type of
# sampling can be selected via sampler.type and sampler.param properties.
# Jaeger libraries support the following samplers:
#
# - Constant (sampler.type=const) sampler always makes the same decision for
# all traces. It either samples all traces (sampler.param=1) or none of
# them (sampler.param=0).
#
# - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
# decision with the probability of sampling equal to the value of
# sampler.param property. For example, with sampler.param=0.1 approximately
# 1 in 10 traces will be sampled.
#
# - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
# limiter to ensure that traces are sampled with a certain constant rate.
# For example, when sampler.param=2.0 it will sample requests with the rate
# of 2 traces per second.
#
# - Remote (sampler.type=remote, which is also the default) sampler consults
# Jaeger agent for the appropriate sampling strategy to use in the current
# service. This allows controlling the sampling strategies in the services
# from a central configuration in Jaeger backend, or even dynamically.
#
sampler:
type: ratelimiting
param: 10.0
reporter:
logSpans: true
localAgentHostPort: localhost:6831

View file

@ -1,4 +0,0 @@
{
"service_name": "CTX",
"collector_host": "localhost"
}

View file

@ -1,38 +0,0 @@
global
# nbthread 1
maxconn 5000
hard-stop-after 10s
stats socket /tmp/haproxy.sock mode 666 level admin
defaults
log global
mode http
option httplog
option dontlognull
option httpclose
retries 3
maxconn 4000
timeout connect 5000
timeout client 50000
timeout server 50000
listen stats
mode http
bind *:8001
stats uri /
stats admin if TRUE
stats refresh 10s
frontend ot-test-ctx-frontend
bind *:10080
mode http
default_backend servers-backend
acl acl-http-status-ok status 100:399
filter opentracing id ot-test-ctx config ctx/ot.cfg
http-response ot-group ot-test-ctx http_response_group if acl-http-status-ok
http-after-response ot-group ot-test-ctx http_after_response_group if !acl-http-status-ok
backend servers-backend
mode http
server server-1 127.0.0.1:8000

View file

@ -1,197 +0,0 @@
[ot-test-ctx]
ot-tracer ot-test-tracer
log localhost:514 local7 debug
config ctx/cfg-jaeger.yml
plugin libjaeger_opentracing_plugin-0.5.0.so
option dontlog-normal
option hard-errors
no option disabled
rate-limit 100.0
groups http_response_group
groups http_after_response_group
scopes client_session_start_1
scopes client_session_start_2
scopes frontend_tcp_request
scopes http_wait_request
scopes http_body_request
scopes frontend_http_request
scopes switching_rules_request
scopes backend_tcp_request
scopes backend_http_request
scopes process_server_rules_request
scopes http_process_request
scopes tcp_rdp_cookie_request
scopes process_sticking_rules_request
scopes client_session_end
scopes server_unavailable
scopes server_session_start
scopes tcp_response
scopes http_wait_response
scopes process_store_rules_response
scopes http_response http_response-error
scopes server_session_end
ot-group http_response_group
scopes http_response_1
scopes http_response_2
ot-scope http_response_1
span "HTTP response"
log "hdr.content" res.hdr("content-type") str("; length: ") res.hdr("content-length") str(" bytes")
ot-scope http_response_2
span "HTTP response"
log "hdr.date" res.hdr("date") str(" / ") res.hdr("last-modified")
ot-group http_after_response_group
scopes http_after_response
ot-scope http_after_response
span "HAProxy response" child-of "HAProxy session"
tag "error" bool(true)
tag "http.status_code" status
ot-scope client_session_start_1
span "HAProxy session" root
inject "ot_ctx_1" use-headers use-vars
baggage "haproxy_id" var(sess.ot.uuid)
event on-client-session-start
ot-scope client_session_start_2
extract "ot_ctx_1" use-vars
span "Client session" child-of "ot_ctx_1"
inject "ot_ctx_2" use-headers use-vars
event on-client-session-start
ot-scope frontend_tcp_request
extract "ot_ctx_2" use-vars
span "Frontend TCP request" child-of "ot_ctx_2"
inject "ot_ctx_3" use-headers use-vars
event on-frontend-tcp-request
ot-scope http_wait_request
extract "ot_ctx_3" use-vars
span "HTTP wait request" follows-from "ot_ctx_3"
inject "ot_ctx_4" use-headers use-vars
finish "Frontend TCP request" "ot_ctx_3"
event on-http-wait-request
ot-scope http_body_request
extract "ot_ctx_4" use-vars
span "HTTP body request" follows-from "ot_ctx_4"
inject "ot_ctx_5" use-headers use-vars
finish "HTTP wait request" "ot_ctx_4"
event on-http-body-request
ot-scope frontend_http_request
extract "ot_ctx_5" use-vars
span "Frontend HTTP request" follows-from "ot_ctx_5"
tag "http.method" method
tag "http.url" url
tag "http.version" str("HTTP/") req.ver
inject "ot_ctx_6" use-headers use-vars
finish "HTTP body request" "ot_ctx_5"
event on-frontend-http-request
ot-scope switching_rules_request
extract "ot_ctx_6" use-vars
span "Switching rules request" follows-from "ot_ctx_6"
inject "ot_ctx_7" use-headers use-vars
finish "Frontend HTTP request" "ot_ctx_6"
event on-switching-rules-request
ot-scope backend_tcp_request
extract "ot_ctx_7" use-vars
span "Backend TCP request" follows-from "ot_ctx_7"
inject "ot_ctx_8" use-headers use-vars
finish "Switching rules request" "ot_ctx_7"
event on-backend-tcp-request
ot-scope backend_http_request
extract "ot_ctx_8" use-vars
span "Backend HTTP request" follows-from "ot_ctx_8"
inject "ot_ctx_9" use-headers use-vars
finish "Backend TCP request" "ot_ctx_8"
event on-backend-http-request
ot-scope process_server_rules_request
extract "ot_ctx_9" use-vars
span "Process server rules request" follows-from "ot_ctx_9"
inject "ot_ctx_10" use-headers use-vars
finish "Backend HTTP request" "ot_ctx_9"
event on-process-server-rules-request
ot-scope http_process_request
extract "ot_ctx_10" use-vars
span "HTTP process request" follows-from "ot_ctx_10"
inject "ot_ctx_11" use-headers use-vars
finish "Process server rules request" "ot_ctx_10"
event on-http-process-request
ot-scope tcp_rdp_cookie_request
extract "ot_ctx_11" use-vars
span "TCP RDP cookie request" follows-from "ot_ctx_11"
inject "ot_ctx_12" use-headers use-vars
finish "HTTP process request" "ot_ctx_11"
event on-tcp-rdp-cookie-request
ot-scope process_sticking_rules_request
extract "ot_ctx_12" use-vars
span "Process sticking rules request" follows-from "ot_ctx_12"
inject "ot_ctx_13" use-headers use-vars
finish "TCP RDP cookie request" "ot_ctx_12"
event on-process-sticking-rules-request
ot-scope client_session_end
finish "Client session" "ot_ctx_2"
event on-client-session-end
ot-scope server_unavailable
finish *
event on-server-unavailable
ot-scope server_session_start
span "Server session" child-of "ot_ctx_1"
inject "ot_ctx_14" use-vars
extract "ot_ctx_13" use-vars
finish "Process sticking rules request" "ot_ctx_13"
event on-server-session-start
ot-scope tcp_response
extract "ot_ctx_14" use-vars
span "TCP response" child-of "ot_ctx_14"
inject "ot_ctx_15" use-vars
event on-tcp-response
ot-scope http_wait_response
extract "ot_ctx_15" use-vars
span "HTTP wait response" follows-from "ot_ctx_15"
inject "ot_ctx_16" use-headers use-vars
finish "TCP response" "ot_ctx_15"
event on-http-wait-response
ot-scope process_store_rules_response
extract "ot_ctx_16" use-vars
span "Process store rules response" follows-from "ot_ctx_16"
inject "ot_ctx_17" use-headers use-vars
finish "HTTP wait response" "ot_ctx_16"
event on-process-store-rules-response
ot-scope http_response
extract "ot_ctx_17" use-vars
span "HTTP response" follows-from "ot_ctx_17"
tag "http.status_code" status
finish "Process store rules response" "ot_ctx_17"
event on-http-response
ot-scope http_response-error
span "HTTP response"
tag "error" bool(true)
event on-http-response if !acl-http-status-ok
ot-scope server_session_end
finish *
event on-server-session-end

View file

@ -1,5 +0,0 @@
{
"service": "EMPTY",
"agent_host": "localhost",
"agent_port": 8126
}

View file

@ -1,34 +0,0 @@
service_name:
EMPTY
###
# When using configuration object to instantiate the tracer, the type of
# sampling can be selected via sampler.type and sampler.param properties.
# Jaeger libraries support the following samplers:
#
# - Constant (sampler.type=const) sampler always makes the same decision for
# all traces. It either samples all traces (sampler.param=1) or none of
# them (sampler.param=0).
#
# - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
# decision with the probability of sampling equal to the value of
# sampler.param property. For example, with sampler.param=0.1 approximately
# 1 in 10 traces will be sampled.
#
# - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
# limiter to ensure that traces are sampled with a certain constant rate.
# For example, when sampler.param=2.0 it will sample requests with the rate
# of 2 traces per second.
#
# - Remote (sampler.type=remote, which is also the default) sampler consults
# Jaeger agent for the appropriate sampling strategy to use in the current
# service. This allows controlling the sampling strategies in the services
# from a central configuration in Jaeger backend, or even dynamically.
#
sampler:
type: ratelimiting
param: 10.0
reporter:
logSpans: true
localAgentHostPort: localhost:6831

View file

@ -1,4 +0,0 @@
{
"service_name": "EMPTY",
"collector_host": "localhost"
}

View file

@ -1,30 +0,0 @@
global
stats socket /tmp/haproxy.sock mode 666 level admin
defaults
log global
mode http
option httplog
option dontlognull
option httpclose
timeout connect 5000
timeout client 50000
timeout server 50000
listen stats
mode http
bind *:8001
stats uri /
stats admin if TRUE
stats refresh 10s
frontend ot-test-empty
bind *:10080
mode http
default_backend servers-backend
filter opentracing id ot-test-empty config empty/ot.cfg
backend servers-backend
mode http
server server-1 127.0.0.1:8000

View file

@ -1,3 +0,0 @@
ot-tracer ot-test-tracer
config empty/cfg-jaeger.yml
plugin libjaeger_opentracing_plugin-0.5.0.so

View file

@ -1,5 +0,0 @@
{
"service": "FE",
"agent_host": "localhost",
"agent_port": 8126
}

View file

@ -1,34 +0,0 @@
service_name:
FE
###
# When using configuration object to instantiate the tracer, the type of
# sampling can be selected via sampler.type and sampler.param properties.
# Jaeger libraries support the following samplers:
#
# - Constant (sampler.type=const) sampler always makes the same decision for
# all traces. It either samples all traces (sampler.param=1) or none of
# them (sampler.param=0).
#
# - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
# decision with the probability of sampling equal to the value of
# sampler.param property. For example, with sampler.param=0.1 approximately
# 1 in 10 traces will be sampled.
#
# - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
# limiter to ensure that traces are sampled with a certain constant rate.
# For example, when sampler.param=2.0 it will sample requests with the rate
# of 2 traces per second.
#
# - Remote (sampler.type=remote, which is also the default) sampler consults
# Jaeger agent for the appropriate sampling strategy to use in the current
# service. This allows controlling the sampling strategies in the services
# from a central configuration in Jaeger backend, or even dynamically.
#
sampler:
type: ratelimiting
param: 10.0
reporter:
logSpans: true
localAgentHostPort: localhost:6831

View file

@ -1,4 +0,0 @@
{
"service_name": "FE",
"collector_host": "localhost"
}

View file

@ -1,37 +0,0 @@
global
# nbthread 1
maxconn 5000
hard-stop-after 10s
# log localhost:514 local7 debug
# debug
stats socket /tmp/haproxy-fe.sock mode 666 level admin
defaults
log global
mode http
option httplog
option dontlognull
option httpclose
retries 3
maxconn 4000
timeout connect 5000
timeout client 50000
timeout server 50000
listen stats
mode http
bind *:8001
stats uri /
stats admin if TRUE
stats refresh 10s
frontend ot-test-fe-frontend
bind *:10080
mode http
default_backend servers-backend
filter opentracing id ot-test-fe config fe/ot.cfg
backend servers-backend
mode http
server server-1 127.0.0.1:11080

View file

@ -1,74 +0,0 @@
[ot-test-fe]
ot-tracer ot-test-tracer
config fe/cfg-jaeger.yml
plugin libjaeger_opentracing_plugin-0.5.0.so
# log localhost:514 local7 debug
option dontlog-normal
option hard-errors
no option disabled
rate-limit 100.0
scopes client_session_start
scopes frontend_tcp_request
scopes frontend_http_request
scopes backend_tcp_request
scopes backend_http_request
scopes client_session_end
scopes server_session_start
scopes tcp_response
scopes http_response
scopes server_session_end
ot-scope client_session_start
span "HAProxy session" root
baggage "haproxy_id" var(sess.ot.uuid)
span "Client session" child-of "HAProxy session"
event on-client-session-start
ot-scope frontend_tcp_request
span "Frontend TCP request" child-of "Client session"
event on-frontend-tcp-request
ot-scope frontend_http_request
span "Frontend HTTP request" follows-from "Frontend TCP request"
tag "http.method" method
tag "http.url" url
tag "http.version" str("HTTP/") req.ver
finish "Frontend TCP request"
event on-frontend-http-request
ot-scope backend_tcp_request
span "Backend TCP request" follows-from "Frontend HTTP request"
finish "Frontend HTTP request"
event on-backend-tcp-request
ot-scope backend_http_request
span "Backend HTTP request" follows-from "Backend TCP request"
finish "Backend TCP request"
span "HAProxy session"
inject "ot-ctx" use-headers
event on-backend-http-request
ot-scope client_session_end
finish "Client session"
event on-client-session-end
ot-scope server_session_start
span "Server session" child-of "HAProxy session"
finish "Backend HTTP request"
event on-server-session-start
ot-scope tcp_response
span "TCP response" child-of "Server session"
event on-tcp-response
ot-scope http_response
span "HTTP response" follows-from "TCP response"
tag "http.status_code" status
finish "TCP response"
event on-http-response
ot-scope server_session_end
finish *
event on-server-session-end

View file

@ -1,5 +0,0 @@
#!/bin/sh
#
test ${#} -lt 1 && exit 1
awk '/ {$/ { sub(/\(.*/, "", $5); print $5 }' "${@}" | sort | uniq -c

View file

@ -1,45 +0,0 @@
#!/bin/sh
#
_ARG_DIR="${1:-.}"
get ()
{
local _arg_tracer="${1}"
local _arg_version="${2}"
local _arg_url="${3}"
local _arg_file="${4}"
local _var_tmpfile="_tmpfile_"
local _var_plugin="lib${_arg_tracer}_opentracing_plugin-${_arg_version}.so"
test -e "${_var_plugin}" && return 0
wget "https://github.com/${_arg_url}/releases/download/v${_arg_version}/${_arg_file}" -O "${_var_tmpfile}" || {
rm "${_var_tmpfile}"
return 1
}
case "$(file ${_var_tmpfile})" in
*shared\ object*)
mv "${_var_tmpfile}" "${_var_plugin}" ;;
*gzip\ compressed\ data*)
gzip -cd "${_var_tmpfile}" > "${_var_plugin}"
rm "${_var_tmpfile}" ;;
esac
}
mkdir -p "${_ARG_DIR}" && cd "${_ARG_DIR}" || exit 1
get dd 1.1.2 DataDog/dd-opentracing-cpp linux-amd64-libdd_opentracing_plugin.so.gz
get dd 1.2.0 DataDog/dd-opentracing-cpp linux-amd64-libdd_opentracing_plugin.so.gz
get jaeger 0.4.2 jaegertracing/jaeger-client-cpp libjaegertracing_plugin.linux_amd64.so
#et jaeger 0.5.0 jaegertracing/jaeger-client-cpp libjaegertracing_plugin.linux_amd64.so
#et jaeger 0.6.0 jaegertracing/jaeger-client-cpp libjaegertracing_plugin.linux_amd64.so
get lightstep 0.12.0 lightstep/lightstep-tracer-cpp linux-amd64-liblightstep_tracer_plugin.so.gz
get lightstep 0.13.0 lightstep/lightstep-tracer-cpp linux-amd64-liblightstep_tracer_plugin.so.gz
get zipkin 0.5.2 rnburn/zipkin-cpp-opentracing linux-amd64-libzipkin_opentracing_plugin.so.gz

View file

@ -1 +0,0 @@
<html><body><p>Did I err?</p></body></html>

View file

@ -1,13 +0,0 @@
#!/bin/sh
#
_ARG_HAPROXY="${1:-$(realpath -L ${PWD}/../../../haproxy)}"
_ARGS="-f cmp/haproxy.cfg"
_LOG_DIR="_logs"
_LOG="${_LOG_DIR}/_log-$(basename "${0}" .sh)-$(date +%s)"
test -x "${_ARG_HAPROXY}" || exit 1
mkdir -p "${_LOG_DIR}" || exit 2
echo "executing: ${_ARG_HAPROXY} ${_ARGS} > ${_LOG}"
"${_ARG_HAPROXY}" ${_ARGS} >"${_LOG}" 2>&1

View file

@ -1,13 +0,0 @@
#!/bin/sh
#
_ARG_HAPROXY="${1:-$(realpath -L ${PWD}/../../../haproxy)}"
_ARGS="-f ctx/haproxy.cfg"
_LOG_DIR="_logs"
_LOG="${_LOG_DIR}/_log-$(basename "${0}" .sh)-$(date +%s)"
test -x "${_ARG_HAPROXY}" || exit 1
mkdir -p "${_LOG_DIR}" || exit 2
echo "executing: ${_ARG_HAPROXY} ${_ARGS} > ${_LOG}"
"${_ARG_HAPROXY}" ${_ARGS} >"${_LOG}" 2>&1

View file

@ -1,47 +0,0 @@
#!/bin/sh
#
_ARG_HAPROXY="${1:-$(realpath -L ${PWD}/../../../haproxy)}"
_ARGS_FE="-f fe/haproxy.cfg"
_ARGS_BE="-f be/haproxy.cfg"
_TIME="$(date +%s)"
_LOG_DIR="_logs"
_LOG_FE="${_LOG_DIR}/_log-$(basename "${0}" fe-be.sh)fe-${_TIME}"
_LOG_BE="${_LOG_DIR}/_log-$(basename "${0}" fe-be.sh)be-${_TIME}"
__exit ()
{
test -z "${2}" && {
echo
echo "Script killed!"
echo "Waiting for jobs to complete..."
pkill --signal SIGUSR1 haproxy
wait
}
test -n "${1}" && {
echo
echo "${1}"
echo
}
exit ${2:-100}
}
trap __exit INT TERM
test -x "${_ARG_HAPROXY}" || __exit "${_ARG_HAPROXY}: executable does not exist" 1
mkdir -p "${_LOG_DIR}" || __exit "${_ARG_HAPROXY}: cannot create log directory" 2
echo "\n------------------------------------------------------------------------"
echo "--- executing: ${_ARG_HAPROXY} ${_ARGS_BE} > ${_LOG_BE}"
"${_ARG_HAPROXY}" ${_ARGS_BE} >"${_LOG_BE}" 2>&1 &
echo "--- executing: ${_ARG_HAPROXY} ${_ARGS_FE} > ${_LOG_FE}"
"${_ARG_HAPROXY}" ${_ARGS_FE} >"${_LOG_FE}" 2>&1 &
echo "------------------------------------------------------------------------\n"
echo "Press CTRL-C to quit..."
wait

View file

@ -1,13 +0,0 @@
#!/bin/sh
#
_ARG_HAPROXY="${1:-$(realpath -L ${PWD}/../../../haproxy)}"
_ARGS="-f sa/haproxy.cfg"
_LOG_DIR="_logs"
_LOG="${_LOG_DIR}/_log-$(basename "${0}" .sh)-$(date +%s)"
test -x "${_ARG_HAPROXY}" || exit 1
mkdir -p "${_LOG_DIR}" || exit 2
echo "executing: ${_ARG_HAPROXY} ${_ARGS} > ${_LOG}"
"${_ARG_HAPROXY}" ${_ARGS} >"${_LOG}" 2>&1

View file

@ -1,5 +0,0 @@
{
"service": "SA",
"agent_host": "localhost",
"agent_port": 8126
}

View file

@ -1,34 +0,0 @@
service_name:
SA
###
# When using configuration object to instantiate the tracer, the type of
# sampling can be selected via sampler.type and sampler.param properties.
# Jaeger libraries support the following samplers:
#
# - Constant (sampler.type=const) sampler always makes the same decision for
# all traces. It either samples all traces (sampler.param=1) or none of
# them (sampler.param=0).
#
# - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
# decision with the probability of sampling equal to the value of
# sampler.param property. For example, with sampler.param=0.1 approximately
# 1 in 10 traces will be sampled.
#
# - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
# limiter to ensure that traces are sampled with a certain constant rate.
# For example, when sampler.param=2.0 it will sample requests with the rate
# of 2 traces per second.
#
# - Remote (sampler.type=remote, which is also the default) sampler consults
# Jaeger agent for the appropriate sampling strategy to use in the current
# service. This allows controlling the sampling strategies in the services
# from a central configuration in Jaeger backend, or even dynamically.
#
sampler:
type: ratelimiting
param: 10.0
reporter:
logSpans: true
localAgentHostPort: localhost:6831

View file

@ -1,4 +0,0 @@
{
"service_name": "SA",
"collector_host": "localhost"
}

View file

@ -1,40 +0,0 @@
global
# nbthread 1
maxconn 5000
hard-stop-after 10s
# log localhost:514 local7 debug
# debug
stats socket /tmp/haproxy.sock mode 666 level admin
defaults
log global
mode http
option httplog
option dontlognull
option httpclose
retries 3
maxconn 4000
timeout connect 5000
timeout client 50000
timeout server 50000
listen stats
mode http
bind *:8001
stats uri /
stats admin if TRUE
stats refresh 10s
frontend ot-test-sa-frontend
bind *:10080
mode http
default_backend servers-backend
acl acl-http-status-ok status 100:399
filter opentracing id ot-test-sa config sa/ot.cfg
http-response ot-group ot-test-sa http_response_group if acl-http-status-ok
http-after-response ot-group ot-test-sa http_after_response_group if !acl-http-status-ok
backend servers-backend
mode http
server server-1 127.0.0.1:8000

View file

@ -1,160 +0,0 @@
[ot-test-sa]
ot-tracer ot-test-tracer
log localhost:514 local7 debug
config sa/cfg-jaeger.yml
plugin libjaeger_opentracing_plugin-0.5.0.so
option dontlog-normal
option hard-errors
no option disabled
rate-limit 100.0
groups http_response_group
groups http_after_response_group
scopes client_session_start
scopes frontend_tcp_request
scopes http_wait_request
scopes http_body_request
scopes frontend_http_request
scopes switching_rules_request
scopes backend_tcp_request
scopes backend_http_request
scopes process_server_rules_request
scopes http_process_request
scopes tcp_rdp_cookie_request
scopes process_sticking_rules_request
scopes client_session_end
scopes server_unavailable
scopes server_session_start
scopes tcp_response
scopes http_wait_response
scopes process_store_rules_response
scopes http_response http_response-error
scopes server_session_end
ot-group http_response_group
scopes http_response_1
scopes http_response_2
ot-scope http_response_1
span "HTTP response"
log "hdr.content" res.hdr("content-type") str("; length: ") res.hdr("content-length") str(" bytes")
ot-scope http_response_2
span "HTTP response"
log "hdr.date" res.hdr("date") str(" / ") res.hdr("last-modified")
ot-group http_after_response_group
scopes http_after_response
ot-scope http_after_response
span "HAProxy response" child-of "HAProxy session"
tag "error" bool(true)
tag "http.status_code" status
ot-scope client_session_start
span "HAProxy session" root
baggage "haproxy_id" var(sess.ot.uuid)
span "Client session" child-of "HAProxy session"
acl acl-test-src-ip src 127.0.0.1
event on-client-session-start if acl-test-src-ip
ot-scope frontend_tcp_request
span "Frontend TCP request" child-of "Client session"
event on-frontend-tcp-request
ot-scope http_wait_request
span "HTTP wait request" follows-from "Frontend TCP request"
finish "Frontend TCP request"
event on-http-wait-request
ot-scope http_body_request
span "HTTP body request" follows-from "HTTP wait request"
finish "HTTP wait request"
event on-http-body-request
ot-scope frontend_http_request
span "Frontend HTTP request" follows-from "HTTP body request"
tag "http.method" method
tag "http.url" url
tag "http.version" str("HTTP/") req.ver
finish "HTTP body request"
event on-frontend-http-request
ot-scope switching_rules_request
span "Switching rules request" follows-from "Frontend HTTP request"
finish "Frontend HTTP request"
event on-switching-rules-request
ot-scope backend_tcp_request
span "Backend TCP request" follows-from "Switching rules request"
finish "Switching rules request"
event on-backend-tcp-request
ot-scope backend_http_request
span "Backend HTTP request" follows-from "Backend TCP request"
finish "Backend TCP request"
event on-backend-http-request
ot-scope process_server_rules_request
span "Process server rules request" follows-from "Backend HTTP request"
finish "Backend HTTP request"
event on-process-server-rules-request
ot-scope http_process_request
span "HTTP process request" follows-from "Process server rules request"
finish "Process server rules request"
event on-http-process-request
ot-scope tcp_rdp_cookie_request
span "TCP RDP cookie request" follows-from "HTTP process request"
finish "HTTP process request"
event on-tcp-rdp-cookie-request
ot-scope process_sticking_rules_request
span "Process sticking rules request" follows-from "TCP RDP cookie request"
finish "TCP RDP cookie request"
event on-process-sticking-rules-request
ot-scope client_session_end
finish "Client session"
event on-client-session-end
ot-scope server_unavailable
finish *
event on-server-unavailable
ot-scope server_session_start
span "Server session" child-of "HAProxy session"
finish "Process sticking rules request"
event on-server-session-start
ot-scope tcp_response
span "TCP response" child-of "Server session"
event on-tcp-response
ot-scope http_wait_response
span "HTTP wait response" follows-from "TCP response"
finish "TCP response"
event on-http-wait-response
ot-scope process_store_rules_response
span "Process store rules response" follows-from "HTTP wait response"
finish "HTTP wait response"
event on-process-store-rules-response
ot-scope http_response
span "HTTP response" follows-from "Process store rules response"
tag "http.status_code" status
finish "Process store rules response"
event on-http-response
ot-scope http_response-error
span "HTTP response"
tag "error" bool(true)
event on-http-response if !acl-http-status-ok
ot-scope server_session_end
finish *
event on-server-session-end

View file

@ -1,117 +0,0 @@
#!/bin/sh
#
_ARG_CFG="${1}"
_ARG_DIR="${2:-${1}}"
_LOG_DIR="_logs"
_HTTPD_PIDFILE="${_LOG_DIR}/thttpd.pid"
_USAGE_MSG="usage: $(basename "${0}") cfg [dir]"
sh_exit ()
{
test -z "${2}" && {
echo
echo "Script killed!"
}
test -n "${1}" && {
echo
echo "${1}"
echo
}
exit ${2:-64}
}
httpd_run ()
{
test -e "${_HTTPD_PIDFILE}" && return
thttpd -p 8000 -d . -nos -nov -l /dev/null -i "${_HTTPD_PIDFILE}"
}
httpd_stop ()
{
test -e "${_HTTPD_PIDFILE}" || return
kill -TERM "$(cat ${_HTTPD_PIDFILE})"
rm "${_HTTPD_PIDFILE}"
}
haproxy_run ()
{
_arg_ratio="${1}"
_var_sed_ot=
_var_sed_haproxy=
if test "${_arg_ratio}" = "disabled"; then
_var_sed_ot="s/no \(option disabled\)/\1/"
elif test "${_arg_ratio}" = "off"; then
_var_sed_haproxy="s/^\(.* filter opentracing .*\)/#\1/g; s/^\(.* ot-group .*\)/#\1/g"
else
_var_sed_ot="s/\(rate-limit\) 100.0/\1 ${_arg_ratio}/"
fi
sed "${_var_sed_haproxy}" "${_ARG_DIR}/haproxy.cfg.in" > "${_ARG_DIR}/haproxy.cfg"
sed "${_var_sed_ot}" "${_ARG_DIR}/ot.cfg.in" > "${_ARG_DIR}/ot.cfg"
if test "${_ARG_DIR}" = "fe"; then
if test "${_arg_ratio}" = "disabled" -o "${_arg_ratio}" = "off"; then
sed "${_var_sed_haproxy}" "be/haproxy.cfg.in" > "be/haproxy.cfg"
sed "${_var_sed_ot}" "be/ot.cfg.in" > "be/ot.cfg"
fi
fi
./run-${_ARG_CFG}.sh &
sleep 5
}
wrk_run ()
{
_arg_ratio="${1}"
echo "--- rate-limit ${_arg_ratio} --------------------------------------------------"
wrk -c8 -d300 -t8 --latency http://localhost:10080/index.html
echo "----------------------------------------------------------------------"
echo
sleep 10
}
command -v thttpd >/dev/null 2>&1 || sh_exit "thttpd: command not found" 5
command -v wrk >/dev/null 2>&1 || sh_exit "wrk: command not found" 6
mkdir -p "${_LOG_DIR}" || sh_exit "${_LOG_DIR}: Cannot create log directory" 1
if test "${_ARG_CFG}" = "all"; then
"${0}" fe-be fe > "${_LOG_DIR}/README-speed-fe-be"
"${0}" sa sa > "${_LOG_DIR}/README-speed-sa"
"${0}" cmp cmp > "${_LOG_DIR}/README-speed-cmp"
"${0}" ctx ctx > "${_LOG_DIR}/README-speed-ctx"
exit 0
fi
test -z "${_ARG_CFG}" -o -z "${_ARG_DIR}" && sh_exit "${_USAGE_MSG}" 4
test -f "run-${_ARG_CFG}.sh" || sh_exit "run-${_ARG_CFG}.sh: No such configuration script" 2
test -d "${_ARG_DIR}" || sh_exit "${_ARG_DIR}: No such directory" 3
test -e "${_ARG_DIR}/haproxy.cfg.in" || cp -af "${_ARG_DIR}/haproxy.cfg" "${_ARG_DIR}/haproxy.cfg.in"
test -e "${_ARG_DIR}/ot.cfg.in" || cp -af "${_ARG_DIR}/ot.cfg" "${_ARG_DIR}/ot.cfg.in"
if test "${_ARG_DIR}" = "fe"; then
test -e "be/haproxy.cfg.in" || cp -af "be/haproxy.cfg" "be/haproxy.cfg.in"
test -e "be/ot.cfg.in" || cp -af "be/ot.cfg" "be/ot.cfg.in"
fi
httpd_run
for _var_ratio in 100.0 50.0 10.0 2.5 0.0 disabled off; do
haproxy_run "${_var_ratio}"
wrk_run "${_var_ratio}"
pkill --signal SIGUSR1 haproxy
wait
done
httpd_stop

View file

@ -259,6 +259,7 @@ listed below. Metrics from extra counters are not listed.
| haproxy_frontend_failed_header_rewriting_total |
| haproxy_frontend_http_cache_lookups_total |
| haproxy_frontend_http_cache_hits_total |
| haproxy_frontend_http_cache_hint_hits_total |
| haproxy_frontend_internal_errors_total |
+-------------------------------------------------+
@ -329,6 +330,7 @@ listed below. Metrics from extra counters are not listed.
| haproxy_backend_connection_reuses_total |
| haproxy_backend_http_cache_lookups_total |
| haproxy_backend_http_cache_hits_total |
| haproxy_backend_http_cache_hint_hits_total |
| haproxy_backend_max_queue_time_seconds |
| haproxy_backend_max_connect_time_seconds |
| haproxy_backend_max_response_time_seconds |

View file

@ -587,6 +587,7 @@ static int promex_dump_front_metrics(struct appctx *appctx, struct htx *htx)
case ST_I_PX_INTERCEPTED:
case ST_I_PX_CACHE_LOOKUPS:
case ST_I_PX_CACHE_HITS:
case ST_I_PX_CACHE_HINT_HITS:
case ST_I_PX_COMP_IN:
case ST_I_PX_COMP_OUT:
case ST_I_PX_COMP_BYP:
@ -1065,6 +1066,7 @@ static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx)
case ST_I_PX_REQ_TOT:
case ST_I_PX_CACHE_LOOKUPS:
case ST_I_PX_CACHE_HITS:
case ST_I_PX_CACHE_HINT_HITS:
case ST_I_PX_COMP_IN:
case ST_I_PX_COMP_OUT:
case ST_I_PX_COMP_BYP:

View file

@ -140,6 +140,8 @@ proposed deployment layout is the following:
| | +-- patches-3.0 # patches from development branch 3.0
| |
| +-- out # report directory (HTML)
| |
| +-- overlay # shared review state (git repo, optional)
|
+- prog
| |
@ -255,6 +257,91 @@ proposed deployment layout is the following:
2 instances for 8 cores / 16 threads gives optimal results on dual memory
channel systems.
- optionally, the review page can share the reviewers' edits (adjusted
verdicts and short notes) between all users of the same server, thanks to a
small CGI script storing them into a dedicated git repository. This part is
entirely optional: without it the page keeps working standalone, edits are
simply lost on reload as before. Three pieces are needed:
- the awk script from dev/patchbot/cgi (the server-side program).
- the storage repository, a plain git working tree which must also stay
outside the served directory. The CGI commits every change into it, so a
committer identity is required (git is the history: "git log" and "git
blame" tell who changed what and when, and the files are plain text, one
line per commit id, trivially hand-editable to fix anything):
mkdir ~/data/overlay
cd ~/data/overlay
git init
git config user.name patchbot
git config user.email patchbot@localhost
cd ~
Note that the example above assumes that the web server will be running
under the same user as the one owning the whole project, which is not
necessarily a great idea. A better approach consists in creating the
repository (and only it) under the web server's owner (e.g. "web" or
"nobody"). Start with the same actions as above, then:
sudo chown -R nobody ~/data/overlay
A group-shared access might also work for server that let the user set a
specific group (not tested). It's important to keep in mind that modern Git
versions are particularly picky about permissions and will even refuse to
read from another user's directory (and the safe.directory trick here
cannot work). If required, a trick consists in forcing GIT_DIR to point to
the .git dir, and GIT_WORK_TREE to the repo (only partially tested). E.g,
for local edition:
cd ~patchbot/data/overlay/
GIT_DIR=$PWD/.git GIT_WORK_TREE=$PWD git commit --amend
or more simply, when installed as nobody:
cd ~patchbot/data/overlay/
sudo -u git commit --amend
Also note that the identity really must be set in the repository's local
config as shown above and cannot be set globally (git config --global)
because CGI processes receive a minimal environment without HOME nor USER,
so git will not be able to read ~/.gitconfig and will fail to guess a valid
identity ("unable to auto-detect email address"), aborting every commit.
If only Git stops working, the file will continue to be updated but no
change history will be kept (a warning will be reported to the UI however).
This might be acceptable in some deployments and might become a deployment
option later if deemed useful.
- the "update.cgi" wrapper, copied from the haproxy tree into a "cgi-bin"
sub-directory in the served directory, next to the generated HTML pages
(the pages reach it with a relative URL). It's a small shell script holding
the local paths, to be adjusted after copying:
mkdir -p -m 711 ~/data/out/cgi-bin/
touch ~/data/out/cgi-bin/index.html
cp ~/data/in/haproxy/dev/patchbot/cgi/update.cgi ~/data/out/cgi-bin/
vi ~/data/out/cgi-bin/update.cgi # point it to the backend and the repo
Note that this wrapper also has a subtle role here: awk can easily die on
some errors (failure to write to certain files etc) without producing a
response. In this case the wrapper will detect it and report the error to
the CGI server so that the browser stays informed of server-side issues.
The web server must be able to execute CGI scripts for this file (e.g.
thttpd needs its "**.cgi" or "*/cgi-bin/*.cgi" pattern for it to be
executed and not served as text, which would expose the paths it contains).
This is the only web-exposed piece; the script and the repository must
never be.
The update-X.Y.sh script deduces the branch from its own file name and
passes it to post-ai.sh (-v) at generation time, which enables the syncing UI
on the page; a page generated without -v simply doesn't show it.
Each save is committed with a subject naming the branch and the first touched
commit, optionally followed with "+ N more" if more commits are updated, with
the full list in the commit body to ease search via git log --grep.
From this point, executing this update script manually should work and produce
the result. Count around 0.5-2 mn per patch on a 8-core machine, so it can be
reasonably fast during the early development stages (before -dev1) but
@ -320,9 +407,14 @@ Using the HTML output is a bit rustic but efficient. The interface is split in
immediately, only after it has spent some time in the development branch),
and "Y" for "Yes" in red (this patch must be backported, let's pick it).
The choice is preselected by the scripts above, and since these are radio
buttons, the user is free to change this selection. Reloading will lose the
user's choices. When changing a selection, the line's background changes to
match a similar color tone, allowing to visually spot preselected patches.
buttons, the user is free to change this selection. A simple reload done
by Ctrl-R will usually preserve user changes because only the document
defines them (radio buttons, input boxes). But a full reload (Ctrl-Shift-R)
will lose the user's choices that were not saved to the server. In any case
the shared states are not retrieved by a full reload, the user has to click
on "Get Updates" to retrieve them. When changing a selection, the line's
background changes to match a similar color tone, allowing to visually
spot preselected patches.
- fifth column: reason for the choice. The scripts try to provide an
explanation for the choice of the preselection, and try to always end with
@ -337,6 +429,52 @@ Using the HTML output is a bit rustic but efficient. The interface is split in
backport to, etc. Elements that look like issues numbers and commit IDs are
turned to links to ease navigation.
When the shared review persistence is deployed, two buttons also appear at the
top right of the page:
- "Get updates" retrieves the current shared state from the server. Nothing
is ever fetched automatically, not even when the page loads: it's up to
the user to click when they want to synchronize. Lines the user has not
touched are updated to reflect the shared verdicts; lines the user has
already changed locally keep their local value (which will overwrite the
shared one on the next save, this is intentional: last opinion wins,
ideally with a note explaining why). Shared notes appear in italics below
the AI explanation.
- "Save changes" pushes the local edits to the server: every verdict that
differs from the last known shared state, plus the pending notes. It is
grayed out whenever there is nothing to save, so a lit button means
unsaved local edits (e.g. right after a reload restoring them). After a
successful save the page is in sync; if the server is busy or
unreachable, the edits simply stay local and a later click retries them.
Notes are edited via the small links below each explanation:
- "[add note]" opens an input box whose text will be appended to the
commit's shared notes (up to 500 characters per addition). Additions
accumulate and cannot conflict, so this is the preferred way to comment.
- "[edit note]" appears once a commit has shared notes: it presents the
whole note in the input box for edition, and saving replaces the note
(emptying the box deletes it). Clicking it while an addition is being
typed merges both so nothing is lost. Since a replacement could destroy
a concurrent update, it is only accepted by the server if the note did
not change in between: otherwise the box turns red and stays in edition,
and "Get updates" rebases it on the current note so it can be revised
(against the refreshed note shown above it) and saved again.
- "[cancel]" closes the box without sending anything.
An open box only disappears once its content is synchronized with the
server: a successful save, an update proving it redundant (e.g. the same
text already pushed from another tab), or an explicit cancel. Boxes
opened by mistake and left unmodified are silently closed by the next
"Get updates".
Note that control characters (0x00-0x1F, 0x7F) are automatically removed from
notes during updates, except for line feed (0x0A) which is replaced with a
space (0x20).
In addition, in order to improve readability, the top of the table shows 4
buttons allowing to show/hide each category. For example, when trying to focus
only on "uncertain" and "wait", it can make sense to hide "N" and "Y" and click

834
dev/patchbot/cgi/update.awk Executable file
View file

@ -0,0 +1,834 @@
#!/usr/bin/gawk -bf
#
# update.awk - storage backend for the patchbot backport review page
#
# Stores the human review edits (verdict overrides and notes) made on the
# patchbot HTML page into a per-branch file kept in a dedicated git
# repository, so that they are durable and shared between reviewers.
#
# This script is exec'd by a tiny "update.cgi" shell wrapper which is the
# only web-exposed piece and holds the deployment-specific configuration:
#
# #!/bin/sh
# exec /path/to/update.awk -r /path/to/repo
#
# The wrapper lives next to the generated HTML pages (thttpd must have the
# "**.cgi" pattern enabled so that it is executed, not served). This script,
# the repository and its .git must all stay OUTSIDE the HTTP document root.
# The repository is a plain git working tree with a configured committer
# identity, containing one file per major branch (e.g. "3.5") with one line
# per touched commit:
#
# <commit_id> [state <n|u|w|y>] [notes "<escaped text>"]
#
# Commit ids are lowercase hex of any length; matching is symmetric-prefix
# (two ids designate the same commit iff one is a prefix of the other) and
# stops on the first match. Notes are double-quoted with '\' escaping '"'
# and '\'; no stored line may ever contain a newline. Malformed fields or
# lines are silently ignored, never fatal, and lines that are not being
# modified are preserved byte-for-byte (admin hand-edits are legal).
#
# Requests (regular CGI environment):
# - POST update.cgi?branch=3.5 with a line-oriented body:
# <cid> state <n|u|w|y|revert>
# <cid> notes <text to append>
# <cid> setnotes <hash> <replacement text>
# "state" overrides the verdict, "revert" (aliases "same", "unchanged")
# removes the override so the bot's verdict applies again, "notes"
# appends to the commit's notes (capped to 500 chars per push). Broken
# directives are dropped, the survivors are applied under the lock and
# the result is committed to git. Neither directive carries a base/old
# value: state is last-write-wins and notes are append-only, which is
# what keeps concurrent edits conflict-free.
# "setnotes" replaces the whole note blob (or deletes it when the text
# is empty) and is the exception: <hash> is the SDBM hash (8 hex
# chars) of the blob the client based its edit on, and the directive is
# only applied if it still matches the stored blob. Otherwise it is
# dropped and reported as a "conflict <cid>" line in the response, so
# that a replacement can never silently destroy a concurrent update.
# - GET update.cgi?branch=3.5 returns the current overlay as a JSON array
# of {"cid": ..., "state": ..., "notes": ...} objects, with absent
# fields omitted and notes fully unescaped (an empty overlay yields
# "[]"), directly usable with JSON.parse() on the client.
#
# Requires GNU awk (PROCINFO, systime); the -b flag in the shebang makes all
# string operations byte-based regardless of the locale, which the escaping,
# the caps and the hash depend on. A few points deserve attention:
# - external commands (git, mkdir, mv, kill) go through /bin/sh, so
# everything interpolated into a command is shell-quoted with q();
# - NUL bytes in inputs are not reliably preserved by awk; they can only
# occur in malformed requests, which are tolerated, not honoured;
# - writing through a redirection whose target cannot be opened is a
# fatal error in awk, terminating us without even a response, so the
# writes into the (stealable) lock dir are arranged to be immune: the
# pid goes through the shell and the temp file is opened the instant
# the lock is acquired, its descriptor surviving a later theft.
#
# The script always exits zero once a response has been emitted, including
# its own error responses, while a fatal awk error exits non-zero without
# any output: a supervising wrapper can thus safely map any non-zero
# termination to a generic error response.
#
# Fully testable from a shell without any HTTP server:
#
# printf '%s\n' 'deadbeef1234 state y' 'deadbeef1234 notes checked' > body
# REQUEST_METHOD=POST QUERY_STRING=branch=3.5 \
# CONTENT_LENGTH=$(stat -c %s body) ./update.awk -r /path/to/repo < body
BEGIN {
MAX_CID_LEN = 40 # bound on a commit id (full SHA-1)
MAX_NOTE_LEN = 500 # cap on a single pushed note addition
MAX_EDIT_LEN = 4000 # cap on a whole-blob replacement (setnotes)
MAX_BRANCH_LEN = 15 # bound on the branch name
MAX_BODY_LEN = 1048576 # bound on a POST body
NOTE_SEP = "; " # separator between coalesced notes
LOCK_RETRIES = 100 # lock attempts before reporting busy
LOCK_SLEEP = 0.05 # sleep between two attempts (~5s total)
LOCK_STALE_AGE = 60 # age backstop when the pid file is unusable
# byte value of every possible character, for escaping decisions
for (i = 0; i < 256; i++)
ORD[sprintf("%c", i)] = i
# Arguments: -r <repo>. Note that when invoked through the shebang,
# gawk itself consumes the leading "-r" (its --re-interval flag, a
# no-op nowadays), and the repository path then reaches us as the
# first bare operand: accept both forms.
repo = ""
for (i = 1; i < ARGC; i++) {
if (ARGV[i] == "-r" && i + 1 < ARGC) {
repo = ARGV[i + 1]
ARGV[i] = ARGV[i + 1] = ""
i++
}
else if (ARGV[i] != "" && repo == "") {
repo = ARGV[i]
ARGV[i] = ""
}
else if (ARGV[i] != "")
die("500 Internal Server Error", \
"server misconfigured (usage: update.awk -r /path/to/repo)")
}
if (repo == "" || system("test -d " q(repo)) != 0)
die("500 Internal Server Error", "server misconfigured")
lock_path = repo "/lock"
lock_pid = lock_path "/pid"
lock_tmp = lock_path "/tmp"
branch = get_branch()
if (ENVIRON["REQUEST_METHOD"] == "POST")
handle_post()
else if (ENVIRON["REQUEST_METHOD"] == "GET")
handle_get()
else
die("405 Method Not Allowed", "unsupported method")
exit 0
}
# shell-quotes <s> so it can be safely interpolated into a command line:
# every external command goes through /bin/sh, this is the only protection.
function q(s)
{
gsub(/'/, "'\\\\''", s)
return "'" s "'"
}
# Prints a complete CGI error response and exits, releasing the lock if it
# was held. Nothing is ever written to stderr, here nor anywhere else:
# thttpd forwards the CGI's stderr to the client *before* its stdout, so
# anything written there would land ahead of the response headers and
# corrupt them; every diagnostic must be carried by the response itself.
function die(status, msg)
{
if (lock_held)
lock_release()
printf "Status: %s\r\nContent-Type: text/plain\r\n\r\n%s\n", status, msg
exit 0
}
# Extracts and validates the "branch" parameter from QUERY_STRING (used for
# both GET and POST). The strict digits-dot-digits pattern is the path
# traversal guard: the branch is the only request-controlled component of
# the storage file path and nothing else may ever reach the path building.
function get_branch( n, i, p, v)
{
n = split(ENVIRON["QUERY_STRING"], p, "&")
v = ""
for (i = 1; i <= n; i++) {
if (substr(p[i], 1, 7) == "branch=") {
v = substr(p[i], 8)
break
}
}
if (v == "" || length(v) > MAX_BRANCH_LEN || v !~ /^[0-9]+\.[0-9]+$/)
die("400 Bad Request", "missing or invalid branch")
return v
}
# SDBM hash of <s> (h = c + h * 65599) as 8 hex chars, the concurrency token
# carried by a note blob replacement; must match the page's JS version. The
# small multiplier keeps the 32-bit state exactly representable with awk's
# double-precision numbers (65599 * 2^32 stays well below 2^53).
function sdbm_hex(s, h, i, n)
{
h = 0
n = length(s)
for (i = 1; i <= n; i++)
h = (ORD[substr(s, i, 1)] + h * 65599) % 4294967296
return sprintf("%08x", h)
}
# Symmetric-prefix commit id match: two ids designate the same commit iff
# one is a prefix of the other. The caller scans in file order and stops on
# the first match; providing enough digits to stay unambiguous is the
# writer's responsibility (12 recommended). A too-short collision merely
# lands on the wrong line and is admin-fixable by editing the file.
function cid_match(a, b)
{
return index(a, b) == 1 || index(b, a) == 1
}
# returns the leading commit id of a storage line (lowercase hex followed by
# a blank or the end of line), or "" if none parses
function line_cid(line, c, nxt)
{
sub(/^[ \t]+/, "", line)
if (!match(line, /^[0-9a-f]+/))
return ""
c = substr(line, 1, RLENGTH)
nxt = substr(line, RLENGTH + 1, 1)
if (length(c) > MAX_CID_LEN || (nxt != "" && nxt != " " && nxt != "\t"))
return ""
return c
}
# Parses storage line <line> into P_cid/P_state/P_notes/P_has_notes. Returns
# 1 if a valid commit id was found (the entry is usable), 0 otherwise. Any
# broken or unknown field is silently dropped, never fatal, so that one bad
# hand-edit cannot break the whole file and future format additions don't
# trip older code.
function parse_line(line, p, v, nxt, out, i, n, c, closed)
{
P_cid = ""; P_state = ""; P_notes = ""; P_has_notes = 0
P_cid = line_cid(line)
if (P_cid == "")
return 0
sub(/^[ \t]+/, "", line)
p = substr(line, length(P_cid) + 1)
while (p != "") {
sub(/^[ \t]+/, "", p)
if (p == "")
break
if (match(p, /^state[ \t]+/)) {
v = substr(p, RLENGTH + 1, 1)
nxt = substr(p, RLENGTH + 2, 1)
if (v ~ /^[nuwy]$/ && (nxt == "" || nxt == " " || nxt == "\t")) {
P_state = v
p = substr(p, RLENGTH + 2)
continue
}
# unknown state value: drop the field
}
else if (match(p, /^notes[ \t]+"/)) {
out = ""; closed = 0
i = RLENGTH + 1
n = length(p)
while (i <= n) {
c = substr(p, i, 1)
if (c == "\"") {
closed = 1
i++
break
}
if (c == "\\" && i < n) {
i++
c = substr(p, i, 1)
}
i++
if (c == "\r")
continue
if (ORD[c] < 32 || ORD[c] == 127)
c = " "
out = out c
}
if (closed) {
P_notes = out
P_has_notes = 1
p = substr(p, i)
continue
}
# unterminated quote: drop the field and what follows,
# it cannot be delimited
break
}
# unknown or broken field: skip one token and try again
sub(/^[^ \t]+/, "", p)
}
return 1
}
# formats an entry back into a storage line. Notes are quoted with '\'
# escaping '"' and '\'; as a hard invariant, no control char (and especially
# no newline) may ever be emitted inside a line, or the one-line-per-commit
# format breaks, so anything unexpected is defensively turned into a space.
function fmt_entry(cid, state, notes, has_notes, line, out, i, n, c)
{
line = cid
if (state != "")
line = line " state " state
if (has_notes) {
out = ""
n = length(notes)
for (i = 1; i <= n; i++) {
c = substr(notes, i, 1)
if (c == "\r")
continue
if (c == "\"" || c == "\\")
out = out "\\" c
else if (ORD[c] < 32 || ORD[c] == 127)
out = out " "
else
out = out c
}
line = line " notes \"" out "\""
}
return line
}
# Sanitises a pushed note: CR is dropped, any other control char becomes a
# space (nothing may ever introduce a newline into a stored line), then the
# text is trimmed and capped to <cap> bytes on a UTF-8 character boundary.
function sanitize_note(s, cap, b)
{
gsub(/\r/, "", s)
gsub(/[\x01-\x1f\x7f]/, " ", s)
sub(/^ +/, "", s)
sub(/ +$/, "", s)
if (length(s) > cap) {
# never cut in the middle of a UTF-8 sequence: back off while
# the first dropped byte is a continuation byte (0x80-0xBF)
b = ORD[substr(s, cap + 1, 1)]
while (cap > 0 && b >= 128 && b < 192) {
cap--
b = ORD[substr(s, cap + 1, 1)]
}
s = substr(s, 1, cap)
sub(/ +$/, "", s)
}
return s
}
# reads the POST body from stdin according to CONTENT_LENGTH
function read_body( cl, body, line, got)
{
if (ENVIRON["CONTENT_LENGTH"] !~ /^[0-9]+$/ || \
ENVIRON["CONTENT_LENGTH"] + 0 > MAX_BODY_LEN)
die("400 Bad Request", "missing or invalid content length")
cl = ENVIRON["CONTENT_LENGTH"] + 0
body = ""; got = 0
while (got < cl && (getline line < "/dev/stdin") > 0) {
got += length(line) + length(RT)
body = body line "\n"
}
if (got < cl)
die("400 Bad Request", "truncated body")
return body
}
# Parses the POST body into the d_* directive arrays. Broken directives are
# dropped, never fatal: a non-hex or over-long cid, an unknown verb or state
# value, or an empty note simply skip that line, and the survivors are still
# applied. Returns the number of valid directives.
function parse_directives(body, nb, n, i, line, cid, rest, v, txt, h)
{
nb = 0
n = split(body, BL, "\n")
for (i = 1; i <= n; i++) {
line = BL[i]
sub(/\r$/, "", line)
sub(/^[ \t]+/, "", line)
# the commit id is stored verbatim as sent by the client
# (whatever length the page carries), only lowercased. The
# length bound is the only enforcement.
if (!match(line, /^[0-9a-fA-F]+[ \t]/))
continue
cid = tolower(substr(line, 1, RLENGTH - 1))
if (length(cid) > MAX_CID_LEN)
continue
rest = substr(line, RLENGTH + 1)
sub(/^[ \t]+/, "", rest)
if (match(rest, /^state[ \t]+/)) {
v = substr(rest, RLENGTH + 1)
sub(/[ \t]+$/, "", v)
if (v ~ /^[nuwy]$/) {
nb++
d_type[nb] = "state"; d_cid[nb] = cid; d_state[nb] = v
}
else if (v ~ /^(revert|same|unchanged)$/) {
nb++
d_type[nb] = "revert"; d_cid[nb] = cid
}
# else: unknown value or trailing junk, drop
}
else if (match(rest, /^notes[ \t]/)) {
txt = sanitize_note(substr(rest, RLENGTH + 1), MAX_NOTE_LEN)
if (txt == "")
continue
nb++
d_type[nb] = "notes"; d_cid[nb] = cid; d_note[nb] = txt
}
else if (match(rest, /^setnotes[ \t]+/)) {
v = substr(rest, RLENGTH + 1)
if (!match(v, /^[0-9a-fA-F]{8}([ \t]|$)/))
continue
h = tolower(substr(v, 1, 8))
txt = substr(v, 9)
sub(/^[ \t]+/, "", txt)
# an empty replacement is valid: it deletes the notes
nb++
d_type[nb] = "setnotes"; d_cid[nb] = cid
d_hash[nb] = h
d_note[nb] = sanitize_note(txt, MAX_EDIT_LEN)
}
}
return nb
}
# loads the branch file into the L_* line arrays; a missing file is an empty
# one (first write will create it). Lines are kept verbatim; only the
# leading commit id is parsed here, for matching.
function load_file(fname, line)
{
nb_lines = 0
while ((getline line < fname) > 0) {
nb_lines++
L_raw[nb_lines] = line
L_cid[nb_lines] = line_cid(line)
L_touched[nb_lines] = 0
}
close(fname)
}
# Applies directive <di>: the target line is looked up by prefix-match,
# first match wins, scanning the file lines then the new entries; a miss
# creates a new entry (except for a revert, which then has nothing to
# remove). A line reduced to neither state nor notes is dropped at write
# time. Returns 0 on success, or 1 when a setnotes base hash doesn't match
# the stored blob anymore: the directive is then not applied (a replacement
# must never silently destroy a concurrent update) and the caller reports
# the conflict.
function apply_directive(di, i, li, ni, was_touched, cur)
{
li = 0; ni = 0; was_touched = 0
for (i = 1; i <= nb_lines; i++) {
if (L_cid[i] == "" || !cid_match(L_cid[i], d_cid[di]))
continue
li = i
was_touched = L_touched[i]
if (!L_touched[i]) {
parse_line(L_raw[i])
L_state[i] = P_state
L_notes[i] = P_notes
L_has[i] = P_has_notes
L_touched[i] = 1
}
break
}
if (!li) {
for (i = 1; i <= nb_new; i++) {
if (cid_match(N_cid[i], d_cid[di])) {
ni = i
break
}
}
}
# The base check happens before any entry creation or modification.
# On conflict the line must be left exactly as found, including not
# marked as modified if this lookup was what materialised it.
if (d_type[di] == "setnotes") {
cur = ""
if (li && L_has[li])
cur = L_notes[li]
else if (ni && N_has[ni])
cur = N_notes[ni]
if (sdbm_hex(cur) != d_hash[di]) {
if (li && !was_touched)
L_touched[li] = 0
return 1
}
}
if (!li && !ni) {
if (d_type[di] == "revert")
return 0 # nothing stored for this commit anyway
if (d_type[di] == "setnotes" && d_note[di] == "")
return 0 # deleting non-existing notes
nb_new++
N_cid[nb_new] = d_cid[di]
N_state[nb_new] = ""; N_notes[nb_new] = ""; N_has[nb_new] = 0
ni = nb_new
}
if (d_type[di] == "state") {
if (li) L_state[li] = d_state[di]; else N_state[ni] = d_state[di]
}
else if (d_type[di] == "revert") {
if (li) L_state[li] = ""; else N_state[ni] = ""
}
else if (d_type[di] == "notes") {
if (li) {
L_notes[li] = L_has[li] ? L_notes[li] NOTE_SEP d_note[di] : d_note[di]
L_has[li] = 1
}
else {
N_notes[ni] = N_has[ni] ? N_notes[ni] NOTE_SEP d_note[di] : d_note[di]
N_has[ni] = 1
}
}
else if (d_type[di] == "setnotes") {
if (li) {
L_notes[li] = d_note[di]
L_has[li] = (d_note[di] != "")
}
else {
N_notes[ni] = d_note[di]
N_has[ni] = (d_note[di] != "")
}
}
return 0
}
# Serialises all writers around the branch files. The lock is a directory
# (mkdir is atomic) at an obvious fixed place, <repo>/lock, which also hosts
# the temp file so that the final rename stays on one filesystem. The
# holder's PID is stored inside; a dead holder is the real staleness signal
# (a live but slow one, e.g. during git gc, must never be evicted
# mid-commit), with a loose age backstop only for when no PID can be read.
#
# NOTE: PID-based takeover is a valid liveness signal only because all
# writers are local children of the same host (thttpd CGI processes). If
# another writer path is ever added (cron job, over-SSH update, push step
# touching this repo), the liveness check stops meaning "the holder is
# alive" and this takeover silently stops protecting the file.
#
# Returns 0 on success, -1 when the lock could not be obtained (busy).
function lock_acquire( i, pid, stale, mt, priv, cmd, p2)
{
for (i = 0; i < LOCK_RETRIES; i++) {
if (system("mkdir " q(lock_path) " 2>/dev/null") == 0) {
# The pid is written via the shell: a takeover based on
# a stale decision may steal this fresh lock before the
# pid lands, and a plain print into the vanished dir
# would be a fatal awk error killing us without even a
# response, while a command failure is just a lost
# acquisition to retry.
if (system("echo " PROCINFO["pid"] " > " q(lock_pid) " 2>/dev/null") != 0)
continue
lock_held = 1
return 0
}
# The lock is held: check whether the holder is still alive.
# All writers run under the same UID, so failing to signal it,
# even with EPERM, means it is dead and its pid was recycled
# by a foreign process; a wrongful eviction would anyway be
# absorbed by the takeover verification and the victim's
# retry. The pid file must be closed even when
# the read fails (e.g. caught empty before the holder flushed
# it): gawk keeps input files open and cached by path, and a
# cached descriptor would keep returning the content of a
# previous lock's deleted pid file, making a live holder look
# dead and letting its lock be stolen in the middle of a write.
pid = ""; stale = 0
getline pid < lock_pid
close(lock_pid)
if (pid "" == PROCINFO["pid"] "")
stale = 1 # our own lock, orphaned by a foiled takeover
else if (pid ~ /^[0-9]+$/ && pid + 0 > 0)
stale = (system("kill -0 " pid " 2>/dev/null") != 0)
else {
cmd = "stat -c %Y " q(lock_path) " 2>/dev/null"
mt = ""
cmd | getline mt
close(cmd)
stale = (mt ~ /^[0-9]+$/ && systime() - mt > LOCK_STALE_AGE)
}
if (stale) {
# Atomic takeover: rename() has exactly one winner
# (mv -T refuses an existing target), which owns the
# recovery and discards the stale dir under its
# private name; a loser re-enters acquisition. Never
# rmdir-then-mkdir, that would race two adopters.
#
# The rename is atomic but the staleness decision was
# not: between reading the dead holder's pid and the
# rename, the lock may have been released and
# re-acquired by a live writer, in which case we just
# stole a live lock. So verify: only discard the
# stolen dir if it still carries the pid we judged
# dead, otherwise put it back untouched (its temp file
# is still inside, the victim never notices anything).
priv = repo "/lock.stale." PROCINFO["pid"] "." i
if (system("mv -T " q(lock_path) " " q(priv) " 2>/dev/null") == 0) {
p2 = ""
getline p2 < (priv "/pid")
close(priv "/pid")
if (p2 "" == pid "")
system("rm -f " q(priv "/pid") " " q(priv "/tmp") \
"; rmdir " q(priv) " 2>/dev/null")
else
# the give-back may fail if the path was
# re-created in between; the private dir is
# then left over for the admin, it cannot
# be restored safely
system("mv -T " q(priv) " " q(lock_path) " 2>/dev/null")
}
continue
}
system("sleep " LOCK_SLEEP)
}
return -1
}
# Runs "git -C <repo> <args>". Both its stdout and stderr are captured (into
# GITMSG, up to 255 bytes, control chars turned into spaces), so that git
# can neither corrupt the CGI response nor leak into a server which sends
# the CGI's stderr to the client, and above all so that the exact git error
# can be reported to the user. Since the command goes through /bin/sh, an
# unfindable git yields status 127 and the shell's own message, reworded to
# directly point at the typical PATH issue.
function run_git(args, cmd, line, out, st)
{
cmd = "git -C " q(repo) " " args " 2>&1"
out = ""
while ((cmd | getline line) > 0)
out = out (out == "" ? "" : " ") line
st = close(cmd)
gsub(/[\x01-\x1f\x7f]/, " ", out)
if (st == 127)
out = "cannot execute git: " out
GITMSG = substr(out, 1, 255)
sub(/ +$/, "", GITMSG)
return st
}
# Releases the lock, but only after checking that it is still ours: after a
# takeover interleaving gone wrong, the path may carry someone else's live
# lock, which must not be dismantled; ours is then a private stale dir that
# the next writers will reclaim. Also called from die().
function lock_release( p)
{
p = ""
getline p < lock_pid
close(lock_pid)
if (p "" == PROCINFO["pid"] "")
system("rm -f " q(lock_pid) " " q(lock_tmp) "; rmdir " q(lock_path) " 2>/dev/null")
lock_held = 0
}
# The GET handler: returns the current overlay for <branch> as a JSON array
# of {"cid","state","notes"} objects with absent fields omitted; a missing
# or empty file yields "[]". The raw storage format never travels: notes
# are unescaped by the parser and JSON-escaped here, so the client can
# JSON.parse() the result and insert notes via textContent directly.
# Unparseable content is silently skipped. Reads are lockless: the atomic
# rename on the write side guarantees the file is always a complete valid
# version.
function handle_get( i, first, out)
{
load_file(repo "/" branch)
printf "Content-Type: application/json\r\nCache-Control: no-store\r\n\r\n"
out = "["
first = 1
for (i = 1; i <= nb_lines; i++) {
if (L_cid[i] == "" || !parse_line(L_raw[i]))
continue
if (P_state == "" && !P_has_notes)
continue # nothing stored for this commit
if (!first)
out = out ","
first = 0
out = out "\n{\"cid\":" json_str(P_cid)
if (P_state != "")
out = out ",\"state\":\"" P_state "\""
if (P_has_notes)
out = out ",\"notes\":" json_str(P_notes)
out = out "}"
}
printf "%s%s]\n", out, first ? "" : "\n"
}
# emits <s> as a JSON string; control chars are defensively encoded and
# UTF-8 sequences pass through verbatim
function json_str(s, out, i, n, c)
{
out = "\""
n = length(s)
for (i = 1; i <= n; i++) {
c = substr(s, i, 1)
if (c == "\"" || c == "\\")
out = out "\\" c
else if (ORD[c] < 32)
out = out sprintf("\\u%04x", ORD[c])
else
out = out c
}
return out "\""
}
# the POST handler: parse directives, and if any survives, apply them to the
# branch file under the lock, atomically replace it and commit it to git.
function handle_post( body, i, j, fname, nb_confl, nb_done, msg, git_failed, attempt, renamed)
{
body = read_body()
nb_dirs = parse_directives(body)
if (nb_dirs == 0) {
# nothing valid remains: complete no-op, no lock taken
printf "Content-Type: text/plain\r\n\r\nOK 0 directives applied\n"
return
}
# The whole locked cycle may have to be redone: if our lock (and the
# temp file inside it) is stolen by a takeover which mistook us for
# dead, the final rename fails while nothing was applied to the file
# yet, so it is always safe to start over from a fresh read.
fname = repo "/" branch
renamed = 0
for (attempt = 0; attempt < 5 && !renamed; attempt++) {
if (lock_acquire() < 0)
die("503 Service Unavailable", "busy, retry")
# Open the temp file right away: from here on its descriptor
# keeps working even if the lock gets stolen (the final rename
# will fail cleanly and be retried). Opening it later would
# widen the window where a theft makes the redirection open
# fail, which is a fatal error in awk.
printf "" > lock_tmp
# the file may contain admin hand-edits, possibly not even committed
# yet: read it as it is, preserve every line we're not touching, and
# commit whatever ends up in the tree.
delete L_raw; delete L_cid; delete L_touched
delete L_state; delete L_notes; delete L_has
delete N_cid; delete N_state; delete N_notes; delete N_has
delete CONFL; delete DONE
load_file(fname)
nb_new = 0; nb_confl = 0; nb_done = 0
for (i = 1; i <= nb_dirs; i++) {
if (apply_directive(i)) {
nb_confl++
CONFL[nb_confl] = d_cid[i]
continue
}
# remember the touched commits for the git commit message
for (j = 1; j <= nb_done; j++)
if (DONE[j] == d_cid[i])
break
if (j > nb_done)
DONE[++nb_done] = d_cid[i]
}
git_failed = 0
GITMSG = ""
renamed = 1
if (nb_confl < nb_dirs) {
# Complete the temp file (opened right when the lock was
# acquired: the open descriptor is immune to a lock theft, the
# writes land in the renamed-away file and the rename below
# then fails cleanly) and atomically rename() it over the
# branch file, so that the latter is always a complete valid
# file, even across a crash.
for (i = 1; i <= nb_lines; i++) {
if (!L_touched[i])
print L_raw[i] > lock_tmp
else if (L_state[i] != "" || L_has[i])
print fmt_entry(L_cid[i], L_state[i], L_notes[i], L_has[i]) > lock_tmp
# else: line reduced to nothing, dropped
}
for (i = 1; i <= nb_new; i++) {
if (N_state[i] != "" || N_has[i])
print fmt_entry(N_cid[i], N_state[i], N_notes[i], N_has[i]) > lock_tmp
}
close(lock_tmp)
system("sync -d " q(lock_tmp) " 2>/dev/null")
if (system("mv -T " q(lock_tmp) " " q(fname) " 2>/dev/null") != 0) {
# our lock was stolen and the temp file went with it:
# what now sits at the lock path belongs to someone
# else, leave it alone and redo the whole cycle from a
# fresh read
lock_held = 0
renamed = 0
continue
}
# The commit subject names the branch and the first touched
# commit (plus how many others), and the body lists all of
# them one per line, which helps a lot when hand-editing the
# storage repository (e.g. to locate a change when rebasing).
msg = "update " branch ": " DONE[1]
if (nb_done > 1)
msg = msg " + " (nb_done - 1) " more"
msg = msg "\n"
for (i = 1; i <= nb_done; i++)
msg = msg "\n" DONE[i]
# Commit failures (e.g. missing committer identity) are not
# fatal: the tree stays valid-but-uncommitted and the next
# writer folds it into its own commit. But they must not stay
# invisible either or the history silently stops being
# recorded, so they are reported as a warning line in the
# response. A no-op (identical content, e.g. re-pushed
# identical states) is not a failure: the commit is simply
# skipped when nothing is staged. Never checkout or reset
# here, it would eat an admin's uncommitted hand-edit.
if (run_git("add -- " q(branch)) != 0)
git_failed = 1
else if (run_git("diff --cached --quiet") != 0 && \
run_git("commit -q -m " q(msg)) != 0)
git_failed = 1
}
# else: everything conflicted, nothing changed, nothing to write
lock_release()
}
if (!renamed)
die("500 Internal Server Error", "cannot replace branch file")
# echo the conflicts then the resulting line(s) after the status; the
# client relies on the "conflict <cid>" lines, the rest is mostly for
# debugging.
printf "Content-Type: text/plain\r\n\r\n"
printf "OK %d directive%s applied\n", nb_dirs - nb_confl, \
nb_dirs - nb_confl == 1 ? "" : "s"
for (i = 1; i <= nb_confl; i++)
print "conflict " CONFL[i]
if (git_failed)
print "warning: git commit failed, history not recorded (" \
(GITMSG != "" ? GITMSG : \
"check the committer identity and permissions in the storage repository") ")"
for (i = 1; i <= nb_lines; i++) {
if (!L_touched[i])
continue
if (L_state[i] != "" || L_has[i])
print fmt_entry(L_cid[i], L_state[i], L_notes[i], L_has[i])
else
print L_cid[i] " removed"
}
for (i = 1; i <= nb_new; i++) {
if (N_state[i] != "" || N_has[i])
print fmt_entry(N_cid[i], N_state[i], N_notes[i], N_has[i])
}
}

View file

@ -0,0 +1,41 @@
#!/bin/sh
# Example deployment wrapper for the storage backend of the patchbot review
# page. This wrapper is the only web-exposed piece: copy it next to the
# generated HTML pages (the page reaches it with a bare relative URL),
# adjust the paths below, and make sure the web server has the "**.cgi"
# pattern enabled so that this file is executed, not served (or its content,
# including the repository path, would leak).
#
# All deployment-specific settings belong here, as arguments: the backend
# itself (update.bin or update.awk, both honour the same interface) and the
# overlay git repository (with a configured committer identity) must both
# stay outside the document root.
#
# The wrapper supervises the backend rather than exec'ing it, relying on a
# simple contract: the backend always exits zero once it has emitted a
# response (including its own error responses), so any other termination
# means no response was produced (e.g. a fatal interpreter error) and a
# generic error is emitted instead, so that the browser always receives a
# response. The backend computes everything before emitting its response in
# one final burst, so a death with partial output is not a realistic case
# (and would anyway yield an unparsable response that the page treats as a
# failed save with the edits kept). The backend's stderr is discarded
# because the web server would send it to the client ahead of the response
# headers and corrupt them; point it to a file instead of /dev/null when
# debugging.
# The web server usually starts CGIs with a restricted compile-time PATH
# (e.g. thttpd's CGI_PATH): make sure it contains git, or adjust it here,
# otherwise nothing gets committed in the storage repository.
PATH=/usr/local/bin:/usr/bin:/bin
export PATH
# where the backend lives (update.awk or update.bin), and the storage repo
SCRIPTS_DIR=/home/patchbot/prog/bin
REPO_DIR=/home/patchbot/data/overlay
if ! "$SCRIPTS_DIR/update.awk" -r "$REPO_DIR" 2>/dev/null; then
printf 'Status: 500 Internal Server Error\r\n'
printf 'Content-Type: text/plain\r\n\r\n'
printf 'backend error, nothing was saved\n'
fi

View file

@ -24,20 +24,30 @@ quit() {
#### Main
USAGE="Usage: ${0##*/} [ -h ] [ -b 'bkp_list' ] patch..."
USAGE="Usage: ${0##*/} [ -h ] [ -b 'bkp_list' ] [ -v version ] patch..."
MYSELF="$0"
GITURL="http://git.haproxy.org/?p=haproxy.git;a=commitdiff;h="
ISSUES="https://github.com/haproxy/haproxy/issues/"
BKP=""
VERSION=""
while [ -n "$1" -a -z "${1##-*}" ]; do
case "$1" in
-h|--help) quit "$USAGE" ;;
-b) BKP="$2"; shift 2 ;;
-v) VERSION="$2"; shift 2 ;;
*) die "$USAGE" ;;
esac
done
# VERSION is the branch this page covers (eg: 3.5). It is only used by the
# in-page JS to sync the review state with the server-side update.cgi, which
# strictly validates it, so let's check it here as well. When empty, the
# syncing UI is not emitted at all and the page keeps working standalone.
if [ -n "$VERSION" ] && ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then
die "Invalid version '$VERSION', expected <digits>.<digits>"
fi
PATCHES=( "$@" )
if [ ${#PATCHES[@]} = 0 ]; then
@ -131,14 +141,474 @@ input.y[type="radio"]:checked {
border: 2px solid black;
background-color: $BT_Y;
}
/* shared reviewers' notes, shown below the AI explanation */
div.notes {
font-style: italic;
margin-top: 2px;
}
</style>
<script type="text/javascript"><!--
var cgi_url = "cgi-bin/update.cgi";
var nb_patches = 0;
var cid = [];
var bkp = [];
// the branch this page covers (eg: "3.5"), used to sync the review state
// with the server-side update.cgi; empty when generated without -v, in
// which case no syncing is possible and the page works standalone.
var branch = '$VERSION';
// Three states exist per line. The original state (orig[]) is the verdict
// the bot chose, captured at load time and constant. The reference state
// (ref_state[]/ref_notes[]) is the last known shared state, i.e. what the
// server last told us, on top of which the user's edits sit; it starts
// equal to the original and is only advanced by "Get updates" and by a
// successful save. The local state is the DOM itself (the checked radios),
// there is no separate copy. Nothing is fetched automatically: the user
// explicitly clicks "Get updates" to retrieve the shared state.
var orig = [];
var ref_state = [];
var ref_notes = [];
var cidmap = {};
// Note edition state per line: mode 0 = closed, 1 = appending (the input
// holds text to add to the shared notes), 2 = editing (the input holds the
// whole replacement blob, and note_base[] snapshots the reference it was
// based on; its hash is sent with the replacement so the server can refuse
// it if the blob changed concurrently). An open input only disappears once
// its content is synchronized with the reference: a successful save, an
// update proving an exact match, or an explicit cancel.
var note_mode = [];
var note_base = [];
// SDBM hash (h = c + h * 65599) of a string's UTF-8 bytes, as 8 hex chars;
// the concurrency token sent with a note replacement. Must match the
// server's C version; the small multiplier keeps the 32-bit state exact
// with JS doubles (65599 * 2^32 stays well below 2^53).
function sdbm(s) {
var b = new TextEncoder().encode(s);
var h = 0;
var i;
for (i = 0; i < b.length; i++)
h = (b[i] + h * 65599) % 4294967296;
return (h + 0x100000000).toString(16).slice(-8);
}
// returns the letter of the checked verdict radio of line <i>, or ""
function cur_state(i) {
if (document.getElementById("bt_" + i + "_n").checked) return "n";
if (document.getElementById("bt_" + i + "_u").checked) return "u";
if (document.getElementById("bt_" + i + "_w").checked) return "w";
if (document.getElementById("bt_" + i + "_y").checked) return "y";
return "";
}
// checks the verdict radio <s> of line <i> (authoritative set, idempotent)
function set_state(i, s) {
var el = document.getElementById("bt_" + i + "_" + s);
if (el)
el.checked = true;
}
// Returns the verdict letter emitted in the page for line <i>, i.e. the
// bot's verdict. It relies on defaultChecked, which reflects the "checked"
// attribute present in the HTML and not the radio's current state: across
// a reload, the browser restores the radios to the user's last local
// state, so looking at cur_state() at load time would wrongly capture the
// user's own edits as being the original state.
function gen_state(i) {
if (document.getElementById("bt_" + i + "_n").defaultChecked) return "n";
if (document.getElementById("bt_" + i + "_u").defaultChecked) return "u";
if (document.getElementById("bt_" + i + "_w").defaultChecked) return "w";
if (document.getElementById("bt_" + i + "_y").defaultChecked) return "y";
return "";
}
// captures the bot's verdicts once the table is fully loaded: they preset
// both the original and the reference states. After a reload, the radios
// (and thus the local state) may differ from the bot's verdicts since the
// browser restores them: this is desired, such differences are unsaved
// local edits and must remain detected as such.
function init_ref() {
var i, el;
for (i = 1; i < nb_patches; i++) {
orig[i] = gen_state(i);
ref_state[i] = orig[i];
ref_notes[i] = "";
cidmap[cid[i]] = i;
// the browser may also have restored an unsaved note into the hidden
// input: reveal it so that it remains visible and editable instead of
// being invisible yet silently pushed on the next save. The edition
// mode did not survive the reload, so assume a plain addition, which
// never destroys anything.
el = document.getElementById("in_" + i);
if (el && el.value) {
note_mode[i] = 1;
el.style.display = "";
}
upd_note_links(i);
}
updt_save_btn();
}
// the status line and the save button exist twice, at the top and at the
// bottom of the page, so both instances are always updated together
function sync_msg(m) {
var el = document.getElementById("sync_msg");
if (el)
el.innerText = m;
el = document.getElementById("sync_msg2");
if (el)
el.innerText = m;
}
// renders the reference notes of line <i> by replacing the whole container
// (never appending), so that re-applying the same notes is idempotent.
function show_notes(i) {
var el = document.getElementById("notes_" + i);
if (el)
el.innerText = ref_notes[i] ? "Notes: " + ref_notes[i] : "";
}
// resolves a commit id received from the server to a line number: exact
// match first (the normal case), then symmetric-prefix (one id is a prefix
// of the other, which only happens with mixed-length ids), first line wins.
// Returns 0 when unknown (eg: a commit which is not on this page).
function find_line(ocid) {
var i;
if (cidmap[ocid])
return cidmap[ocid];
for (i = 1; i < nb_patches; i++)
if (cid[i].startsWith(ocid) || ocid.startsWith(cid[i]))
return i;
return 0;
}
// Applies a freshly fetched overlay (the complete list of shared entries).
// The new reference of every line is recomputed as "the server's entry if
// any, otherwise the bot's original verdict", so that an entry removed on
// the server properly falls back to the original. The reference always
// advances, but the displayed state only moves where the user had no local
// edit: local edits win and will overwrite the shared state at save time.
function apply_ref(list) {
var over_state = [], over_notes = [], claimed = [];
var i, j, e, el, newref, newnotes, oldnotes;
for (j = 0; j < list.length; j++) {
e = list[j];
i = find_line(String(e.cid));
if (!i || claimed[i])
continue;
claimed[i] = 1;
if (e.state)
over_state[i] = String(e.state);
if (e.notes)
over_notes[i] = String(e.notes);
}
for (i = 1; i < nb_patches; i++) {
newref = over_state[i] ? over_state[i] : orig[i];
if (newref != ref_state[i] && ref_state[i] == cur_state(i))
set_state(i, newref);
ref_state[i] = newref;
oldnotes = ref_notes[i];
newnotes = over_notes[i] ? over_notes[i] : "";
if (newnotes != oldnotes) {
ref_notes[i] = newnotes;
show_notes(i);
}
// Reconcile an open note box with the new reference: a box whose
// content is now synchronized disappears (an addition someone already
// pushed, or an edition matching the current notes); an edition whose
// base moved is re-based on the new reference and marked red so the
// user reviews it against the updated notes above before saving.
if (note_mode[i]) {
el = document.getElementById("in_" + i);
if (note_mode[i] == 1 && el.value &&
newnotes == (oldnotes ? oldnotes + "; " + el.value : el.value)) {
cancel_note(i);
}
else if (note_mode[i] == 2) {
if (el.value == newnotes)
cancel_note(i);
else if (note_base[i] != newnotes) {
note_base[i] = newnotes;
mark_conflict(i, 1);
}
}
}
upd_note_links(i);
}
updt_table(0);
updt_output();
updt_save_btn();
}
// "Get updates" button: fetches the current shared state from the server
function fetch_ref() {
var i, el;
if (!branch)
return;
// first silently close the no-op note boxes (opened but nothing changed,
// e.g. an "edit" clicked by mistake): they hold nothing worth preserving
// and would otherwise ambiguously survive while the notes displayed
// above them change.
for (i = 1; i < nb_patches; i++) {
if (!note_mode[i])
continue;
el = document.getElementById("in_" + i);
if (note_mode[i] == 1 && !el.value.trim())
cancel_note(i);
else if (note_mode[i] == 2 && el.value == note_base[i])
cancel_note(i);
}
sync_msg("fetching...");
fetch(cgi_url + "?branch=" + branch)
.then(function(r) { if (!r.ok) throw 0; return r.json(); })
.then(function(list) { apply_ref(list); sync_msg("reference updated"); })
.catch(function() { sync_msg("fetch failed (server unreachable?)"); });
}
// shows/hides the per-line note links according to the edition mode and to
// the presence of reference notes ("edit note" needs something to edit, or
// a pending addition to merge; "cancel" needs an open input)
function upd_note_links(i) {
var m = note_mode[i] ? note_mode[i] : 0;
var el;
el = document.getElementById("ln_add_" + i);
if (el)
el.style.display = m == 0 ? "" : "none";
el = document.getElementById("ln_edit_" + i);
if (el)
el.style.display = (m == 0 && ref_notes[i]) || m == 1 ? "" : "none";
el = document.getElementById("ln_cancel_" + i);
if (el)
el.style.display = m != 0 ? "" : "none";
}
// marks/unmarks the note input of line <i> as conflicting (red): the
// reference changed under the edit, the user must review the current notes
// above against the input's content before saving again.
function mark_conflict(i, on) {
var el = document.getElementById("in_" + i);
if (el)
el.style.backgroundColor = on ? "#ffc0c0" : "";
}
// "[add note]" link: reveals the extra-notes input of line <i> in append
// mode; the text it holds will be appended to the shared notes on save
function add_note(i) {
var el = document.getElementById("in_" + i);
if (!el)
return;
note_mode[i] = 1;
el.maxLength = 500;
el.style.display = "";
el.focus();
upd_note_links(i);
updt_save_btn();
}
// "[edit note]" link: switches line <i> to edition of the whole note blob
// (which a save sends as a replacement); a pending addition is merged in so
// nothing typed so far is lost. The reference blob is snapshotted as the
// base of the edit for the conflict detection.
function edit_note(i) {
var el = document.getElementById("in_" + i);
var txt;
if (!el)
return;
txt = ref_notes[i];
if (note_mode[i] == 1 && el.value)
txt = txt ? txt + "; " + el.value : el.value;
note_mode[i] = 2;
note_base[i] = ref_notes[i];
el.value = txt;
el.maxLength = 4000;
el.style.display = "";
el.focus();
upd_note_links(i);
updt_save_btn();
}
// "[cancel]" link: closes the note input of line <i> without sending
// anything; also used internally once an input is known synchronized.
function cancel_note(i) {
var el = document.getElementById("in_" + i);
if (el) {
el.value = "";
el.style.display = "none";
}
note_mode[i] = 0;
note_base[i] = "";
mark_conflict(i, 0);
upd_note_links(i);
updt_save_btn();
}
// Grays the "Save changes" button when nothing differs from the reference
// (no verdict change, no pending note addition or edition), so it is
// visible at a glance whether anything remains to be saved. Called after
// every action which may change that: verdict clicks, note box openings,
// closings and typing, updates and saves. Bails out at the first pending
// change so the common case stays cheap.
function updt_save_btn() {
var btn = document.getElementById("save_btn");
var btn2 = document.getElementById("save_btn2");
var pending = false;
var i, s, el;
if (!btn)
return;
for (i = 1; i < nb_patches && !pending; i++) {
s = cur_state(i);
if (s && s != ref_state[i])
pending = true;
else if (note_mode[i]) {
el = document.getElementById("in_" + i);
if (note_mode[i] == 1 ? el.value.trim() != "" : el.value != note_base[i])
pending = true;
}
}
btn.disabled = !pending;
if (btn2)
btn2.disabled = !pending;
}
// "Save changes" button: pushes the local edits, i.e. the states differing
// from the reference, the non-empty note additions, and the note editions
// differing from their base; the reference advances on success (failed
// saves keep everything local for a retry). States are last-write-wins and
// additions are append-only, so they cannot conflict; an edition carries
// the hash of its base blob and the server refuses it if the blob changed
// concurrently, reporting "conflict <cid>" lines that turn the concerned
// inputs red (still in edition; "Get updates" re-bases them for revision).
// Note that a state moved back to the reference by hand is simply not sent.
function save_ref() {
var st = [], nt = [], rp = [], rp_on = [];
var body = "", nsent = 0, i, s, el, txt;
if (!branch)
return;
for (i = 1; i < nb_patches; i++) {
s = cur_state(i);
if (s && s != ref_state[i]) {
st[i] = s;
body += cid[i] + " state " + s + "\n";
nsent++;
}
el = document.getElementById("in_" + i);
txt = el ? el.value.replace(/\r/g, "").replace(/[\x00-\x1f\x7f]/g, " ").trim() : "";
if (note_mode[i] == 2) {
// whole-blob replacement, possibly empty (deletion); only sent when
// it differs from the base it was computed from
if (txt != note_base[i]) {
rp[i] = txt;
rp_on[i] = 1;
body += cid[i] + " setnotes " + sdbm(note_base[i]) + " " + txt + "\n";
nsent++;
}
}
else if (txt) {
nt[i] = txt;
body += cid[i] + " notes " + txt + "\n";
nsent++;
}
}
if (!body) {
sync_msg("nothing to save");
return;
}
sync_msg("saving...");
fetch(cgi_url + "?branch=" + branch, { method: "POST", body: body })
.then(function(r) { if (!r.ok) throw 0; return r.text(); })
.then(function(t) {
var confl = {};
var resp = t.split("\n");
var i, j, ok = null, warn = "", nbc = 0;
// The lines of interest may be surrounded by unrelated output (some
// servers leak the CGI's stderr into the response), so scan for them
// anywhere: the count ("OK <n> ..."), the conflicts and the warnings.
for (j = 0; j < resp.length; j++) {
if (resp[j].indexOf("conflict ") == 0) {
i = find_line(resp[j].slice(9).trim());
if (i && !confl[i]) {
confl[i] = 1;
nbc++;
}
}
else if (!ok)
ok = resp[j].match(/^OK (\d+) /);
if (resp[j].indexOf("warning: ") == 0)
warn = "; " + resp[j];
}
// The server states how many directives it applied: anything neither
// applied nor reported as a conflict was silently ignored (e.g. an
// outdated update.bin not knowing a directive). In that case believe
// the server, not ourselves: advance nothing, keep every edit local
// for a retry, and say what happened.
if (!ok || parseInt(ok[1], 10) != nsent - nbc) {
for (i = 1; i < nb_patches; i++) {
if (confl[i])
mark_conflict(i, 1);
}
console.log("unexpected update.cgi response: " + t);
sync_msg("server applied only " + (ok ? ok[1] : "?") + " of " + nsent +
" changes (outdated update.cgi/update.bin?), edits kept" + warn);
return;
}
for (i = 1; i < nb_patches; i++) {
if (st[i])
ref_state[i] = st[i];
if (nt[i]) {
// mirror the server-side coalescing so the display is right
// without refetching; the next fetch trues it up anyway
ref_notes[i] = ref_notes[i] ? ref_notes[i] + "; " + nt[i] : nt[i];
show_notes(i);
cancel_note(i);
}
else if (rp_on[i]) {
if (confl[i]) {
// replacement refused, the blob changed server-side: stay in
// edition and flag it, "Get updates" re-bases it for revision
mark_conflict(i, 1);
}
else {
ref_notes[i] = rp[i];
show_notes(i);
cancel_note(i);
}
}
}
sync_msg((nbc ? "saved, but " + nbc + " note conflict(s): use Get updates and revise the red one(s)" : "saved") + warn);
updt_save_btn();
})
.catch(function() { sync_msg("save failed (busy?), edits kept"); });
}
// first line to review
var review = 0;
@ -255,6 +725,7 @@ function updt(line,value) {
}
updt_table(line);
updt_output();
updt_save_btn();
}
function show_only(b,n,u,w,y) {
@ -267,12 +738,42 @@ function show_only(b,n,u,w,y) {
updt(0,"r");
}
// Resynchronizes the review variable with the checked review radio: across
// a reload, the browser restores the radios to the user's last selection
// (e.g. "All") while the variable is regenerated to the default first line
// to review, and the listing would not match the checked radio anymore.
function init_review() {
var i, el;
if (document.getElementById("show_all").checked) {
review = 0;
return;
}
for (i = 1; i <= nb_patches; i++) {
el = document.getElementById("rv_" + i);
if (el && el.checked) {
review = i;
return;
}
}
}
// -->
</script>
</HEAD>
EOF
echo "<BODY>"
# the syncing UI is only emitted when the branch is known; the page reaches
# update.cgi with a bare relative URL so it must sit in the same directory.
if [ -n "$VERSION" ]; then
echo -n "<div style='float: right; text-align: right;'>"
echo -n "<button onclick='fetch_ref();' title='Retrieve the latest shared review state'>Get updates</button> "
echo -n "<button id='save_btn' disabled onclick='save_ref();' title='Push your local edits to the shared state'>Save changes</button>"
echo "<br/><small id='sync_msg'></small></div>"
fi
echo -n "<table cellpadding=3 cellspacing=5 style='font-size: 150%;'><tr><th align=left>Backported</th>"
echo -n "<td style='background-color:$BG_N'><a href='#' onclick='show_only(1,1,0,0,0);'> N: <span id='cnt_bn'>0</span> </a></td>"
echo -n "<td style='background-color:$BG_U'><a href='#' onclick='show_only(1,0,1,0,0);'> U: <span id='cnt_bu'>0</span> </a></td>"
@ -383,8 +884,11 @@ for patch in "${PATCHES[@]}"; do
resp=$(echo "$resp" | sed -e "s|\([0-9a-f]\{7,40\}\)|<a href='${GITURL}\1'>\1</a>|g")
echo -n "<TD nowrap align=center ${bkp[$cid]:+style='background-color:${BG_B}'}>$seq_num<BR/>"
echo -n "<input type='radio' name='review' onclick='updt($seq_num,\"r\");' ${do_check:+checked} title='Start review here'/></TD>"
echo -n "<TD nowrap ${bkp[$cid]:+style='background-color:${BG_B}'}><tt><a href='${GITURL}${cid}'>$cid</a></tt>${date:+<br/><small style='font-weight:normal'>$date</small>}</TD>"
echo -n "<input type='radio' name='review' id='rv_$seq_num' onclick='updt($seq_num,\"r\");' ${do_check:+checked} title='Start review here'/></TD>"
# only the first 8 chars of the commit id are displayed (enough to be
# unambiguous on one page); everything keyed (href, name=, cid[])
# carries the full id produced by the pipeline, whatever its length.
echo -n "<TD nowrap ${bkp[$cid]:+style='background-color:${BG_B}'}><tt><a href='${GITURL}${cid}'>${cid:0:8}</a></tt>${date:+<br/><small style='font-weight:normal'>$date</small>}</TD>"
echo -n "<TD nowrap><a href='${GITURL}${cid}'>${pnum:+$pnum }$subj</a>${author:+<br/><div align=right><small style='font-weight:normal'>$author</small></div>}</TD>"
echo -n "<TD nowrap align=center>"
echo -n "<input type='radio' onclick='updt($seq_num,\"n\");' id='bt_${seq_num}_n' class='n' name='$cid' value='n' title='Drop' $( [ "$verdict" != no ] || echo -n checked) />"
@ -392,7 +896,18 @@ for patch in "${PATCHES[@]}"; do
echo -n "<input type='radio' onclick='updt($seq_num,\"w\");' id='bt_${seq_num}_w' class='w' name='$cid' value='w' title='wait in -next' $([ "$verdict" != wait ] || echo -n checked) />"
echo -n "<input type='radio' onclick='updt($seq_num,\"y\");' id='bt_${seq_num}_y' class='y' name='$cid' value='y' title='Pick' $( [ "$verdict" != yes ] || echo -n checked) />"
echo -n "</TD>"
echo -n "<TD>$resp</TD>"
# the div is the dedicated container for the shared reviewers' notes,
# filled by full replacement (never appended to) by the JS; the
# hidden input receives the user's own note to be pushed on save
echo -n "<TD>$resp<div class='notes' id='notes_$seq_num'></div>"
if [ -n "$VERSION" ]; then
echo -n "<a href='#' onclick='add_note($seq_num); return false;' id='ln_add_$seq_num' title='Add a shared note to this commit'><small>[add note]</small></a>"
echo -n " <a href='#' onclick='edit_note($seq_num); return false;' id='ln_edit_$seq_num' style='display:none' title='Edit or delete the whole note'><small>[edit note]</small></a>"
echo -n " <a href='#' onclick='cancel_note($seq_num); return false;' id='ln_cancel_$seq_num' style='display:none' title='Abort this note edition'><small>[cancel]</small></a>"
echo -n " <input type='text' id='in_$seq_num' maxlength='500' size='80' style='display:none' oninput='updt_save_btn();' />"
fi
echo -n "</TD>"
echo "</TR>"
echo
((seq_num++))
@ -406,12 +921,23 @@ for patch in "${PATCHES[@]}"; do
fi
done
echo "<TR><TH>New<br/><input type='radio' name='review' onclick='updt($seq_num,\"r\");' ${do_check:+checked} title='Nothing to backport'/></TH><TH>CID</TH><TH>Subject</TH><TH>Verdict<BR>N U W Y</BR></TH><TH>Reason</TH></TR>"
echo "<TR><TH>New<br/><input type='radio' name='review' id='rv_$seq_num' onclick='updt($seq_num,\"r\");' ${do_check:+checked} title='Nothing to backport'/></TH><TH>CID</TH><TH>Subject</TH><TH>Verdict<BR>N U W Y</BR></TH><TH>Reason</TH></TR>"
echo "</TABLE>"
# a copy of the syncing buttons at the bottom right: that's where the user
# ends up after a review, far from the top ones, and forgetting to save the
# work is too easy when no button remains in sight
if [ -n "$VERSION" ]; then
echo -n "<div style='float: right; text-align: right;'>"
echo -n "<button onclick='fetch_ref();' title='Retrieve the latest shared review state'>Get updates</button> "
echo -n "<button id='save_btn2' disabled onclick='save_ref();' title='Push your local edits to the shared state'>Save changes</button>"
echo "<br/><small id='sync_msg2'></small></div>"
fi
echo "<P/>"
echo "<H3>Output:</H3>"
echo "<textarea cols=120 rows=10 id='output'></textarea>"
echo "<P/>"
echo "<script type='text/javascript'>nb_patches=$seq_num; review=$review; updt_table(0); updt_output();</script>"
echo "<script type='text/javascript'>nb_patches=$seq_num; review=$review; init_review(); init_ref(); updt_table(0); updt_output();</script>"
echo "</BODY></HTML>"

View file

@ -16,6 +16,14 @@ if [ -z "$BRANCH" ]; then
exit 1
fi
# The version is deduced from this script's name (update-3.0.sh -> 3.0), so
# that a symlink with another version continues to work as-is. It's passed
# to post-ai.sh for the page to know which branch to sync with update.cgi;
# if the name doesn't match, no version is passed and the page simply won't
# offer syncing.
VERSION="${0##*/}"; VERSION="${VERSION#update-}"; VERSION="${VERSION%.sh}"
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+$ ]] || VERSION=""
# eg: for v3.0-dev0^ we should get v2.9.0 hence "2.9"
STABLE=$(cd "$HAPROXY_DIR" && git describe --tags "v${BRANCH}-dev0^" |cut -f1,2 -d.|cut -f2- -dv)
@ -64,4 +72,4 @@ fi
time EXT=m7bv02.txt MODEL=${MODELS_DIR}/mistral-7b-instruct-v0.2.Q5_K_M.gguf CACHE=${PROMPTS_DIR}/prompt-${BRANCH}-m7bv02.cache PROMPT_PFX=${PROMPTS_DIR}/prompt15-${BRANCH}-mist7bv2-pfx.txt PROMPT_SFX=${PROMPTS_DIR}/prompt15-${BRANCH}-mist7bv2-sfx.txt MAINPROG=$MAINPROG PROGRAM="$SCRIPTS_DIR"/process-patch-v15.sh "$SCRIPTS_DIR"/submit-ai.sh -s ${PARALLEL_RUNS} ${PATCHES_DIR}/*.patch
# generate the output, takes 3-5 seconds
"$SCRIPTS_DIR"/post-ai.sh -b "${BKP[*]}" ${PATCHES_DIR}/*.m7bv02.txt > ${VERDICT_DIR}/verdict-${BRANCH}-m7bv02.html
"$SCRIPTS_DIR"/post-ai.sh ${VERSION:+-v "$VERSION"} -b "${BKP[*]}" ${PATCHES_DIR}/*.m7bv02.txt > ${VERDICT_DIR}/verdict-${BRANCH}-m7bv02.html

View file

@ -3,7 +3,7 @@
Configuration Manual
----------------------
version 3.5
2026/06/25
2026/07/08
This document covers the configuration language as implemented in the version
@ -970,9 +970,9 @@ HAProxy is launched in master-worker mode.
and it may as well condition some worker process parameters (see examples
from section 2.4 "Conditional blocks").
In standalone mode (without "-W" option nor the "master-worker" keyword) the
process behaves like a worker, except for variables "HAPROXY_MASTER_CLI" and
"HAPROXY_MWORKER" which are not defined.
In standalone mode (without "-W" option) the process behaves like a worker,
except for variables "HAPROXY_MASTER_CLI" and "HAPROXY_MWORKER" which are not
defined.
Some variables are marked as not usable and not modifiable:
@ -1255,8 +1255,6 @@ Example:
# 2.3. Environment variables). Its presence enables an additional listener.
global
master-worker
.if defined(HAPROXY_MWORKER)
listen mwcli_px
bind :1111
@ -1269,8 +1267,6 @@ Example:
# mworker-max-reloads parameter.
global
master-worker
.if streq("$HAPROXY_BRANCH",3.1)
mworker-max-reloads 5
.endif
@ -1955,10 +1951,6 @@ The following keywords are supported in the "global" section :
- tune.quic.be.stream.rxbuf
- tune.quic.be.tx.pacing
- tune.quic.be.tx.udp-gso
- tune.quic.cc.cubic.min-losses (deprecated)
- tune.quic.cc-hystart (deprecated)
- tune.quic.disable-tx-pacing (deprecated)
- tune.quic.disable-udp-gso (deprecated)
- tune.quic.fe.cc.cubic-min-losses
- tune.quic.fe.cc.hystart
- tune.quic.fe.cc.max-frame-loss
@ -1974,18 +1966,8 @@ The following keywords are supported in the "global" section :
- tune.quic.fe.stream.rxbuf
- tune.quic.fe.tx.pacing
- tune.quic.fe.tx.udp-gso
- tune.quic.frontend.max-data-size (deprecated)
- tune.quic.frontend.max-idle-timeout (deprecated)
- tune.quic.frontend.max-streams-bidi (deprecated)
- tune.quic.frontend.max-tx-mem (deprecated)
- tune.quic.frontend.stream-data-ratio (deprecated)
- tune.quic.frontend.default-max-window-size (deprecated)
- tune.quic.listen
- tune.quic.max-frame-loss (deprecated)
- tune.quic.mem.tx-max
- tune.quic.reorder-ratio (deprecated)
- tune.quic.retry-threshold (deprecated)
- tune.quic.socket-owner (deprecated)
- tune.quic.zero-copy-fwd-send
- tune.renice.runtime
- tune.renice.startup
@ -3050,32 +3032,6 @@ lua-prepend-path <string> [<type>]
See https://www.lua.org/pil/8.1.html for the details within the Lua
documentation.
master-worker (deprecated)
Master-worker mode. It is equivalent to the command line "-W" argument.
This keyword is deprecated, please start in master-worker mode using "-W" or
"-Ws".
This mode will launch a "master" which will fork a "worker" after reading the
configuration to process the traffic. The master is used as a process manager
which will monitor the "workers".
Using this mode, you can reload HAProxy directly by sending a SIGUSR2 signal
to the master. Reloading will ask the master to read the configuration again
and fork a new worker. The previous worker will be kept until the end of its
jobs.
The master-worker mode is compatible either with the foreground or daemon
mode.
By default, if a worker exits with a bad return code, in the case of a
segfault for example, all workers will be killed, and the master will leave.
It is convenient to combine this behavior with Restart=on-failure in a
systemd unit file in order to relaunch the whole process. If you don't want
this behavior, you must use the keyword "no-exit-on-failure".
See also "-W" in the management guide.
master-worker no-exit-on-failure
In master-worker mode, by default, if a worker exits with a bad return code,
in the case of a segfault for example, all workers will be killed, and the
@ -3171,7 +3127,7 @@ pidfile <pidfile>
Writes PIDs of all daemons into file <pidfile> when daemon mode or writes PID
of master process into file <pidfile> when master-worker mode. This option is
equivalent to the "-p" command line argument. The file must be accessible to
the user starting the process. See also "daemon" and "master-worker".
the user starting the process. See also "daemon".
pp2-never-send-local
A bug in the PROXY protocol v2 implementation was present in HAProxy up to
@ -5142,22 +5098,12 @@ tune.quic.fe.cc.cubic-min-losses <number>
compare some metrics. Never go beyond 2 without an expert's prior analysis of
the situation. The default and minimum value is 1. Always use 1.
tune.quic.cc.cubic.min-losses <number> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.cc.hystart { on | off }
tune.quic.fe.cc.hystart { on | off }
Enables ('on') or disabled ('off') the HyStart++ (RFC 9406) algorithm for
QUIC connections used as a replacement for the slow start phase of congestion
control algorithms which may cause high packet loss. It is disabled by default.
tune.quic.cc-hystart { on | off } (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.cc.max-frame-loss <number>
tune.quic.fe.cc.max-frame-loss <number>
Sets the limit for which a single QUIC frame can be marked as lost. If
@ -5165,11 +5111,6 @@ tune.quic.fe.cc.max-frame-loss <number>
The default value is 10.
tune.quic.max-frame-loss <number> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.cc.max-win-size <size>
tune.quic.fe.cc.max-win-size <size>
Sets the default maximum window size for the congestion controller of a
@ -5185,11 +5126,6 @@ tune.quic.fe.cc.max-win-size <size>
See also the "quic-cc-algo" bind and server options.
tune.quic.frontend.default-max-window-size <size> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.cc.reorder-ratio <0..100, in percent>
tune.quic.fe.cc.reorder-ratio <0..100, in percent>
The ratio applied to the packet reordering threshold calculated. It may
@ -5197,11 +5133,6 @@ tune.quic.fe.cc.reorder-ratio <0..100, in percent>
The default value is 50.
tune.quic.reorder-ratio <0..100, in percent> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.max-idle-timeout <timeout>
tune.quic.fe.max-idle-timeout <timeout>
Sets the QUIC max_idle_timeout transport parameters on either frontend or
@ -5215,11 +5146,6 @@ tune.quic.fe.max-idle-timeout <timeout>
The default value is 30s.
tune.quic.frontend.max-idle-timeout <timeout> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.sec.glitches-threshold <number>
tune.quic.fe.sec.glitches-threshold <number>
Sets the threshold for the number of glitches per connection either on
@ -5235,11 +5161,6 @@ tune.quic.fe.sec.glitches-threshold <number>
See also: fc_glitches, tune.glitches.kill.cpu-usage
tune.quic.frontend.glitches-threshold <number> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.fe.sec.retry-threshold <number>
Dynamically enables the Retry feature for all the configured QUIC listeners
as soon as this number of half open connections is reached. A half open
@ -5254,11 +5175,6 @@ tune.quic.fe.sec.retry-threshold <number>
See https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1.2 for more
information about QUIC retry.
tune.quic.retry-threshold <number> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.fe.sock-per-conn { default-on | force-off }
Specifies globally how QUIC frontend connections will use socket for
receive/send operations. Connections can share listener socket or each
@ -5288,12 +5204,6 @@ tune.quic.fe.sock-per-conn { default-on | force-off }
"force-off" is used globally, it will be applied on every listener instance,
regardless of their individual configuration.
tune.quic.socket-owner { connection | listener } (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. The newer
option is named "tune.quic.fe.sock-per-conn", with legacy value "connection"
corresponding to "default-on" and "listener" to "force-off".
tune.quic.be.stream.data-ratio <0..100, in percent>
tune.quic.fe.stream.data-ratio <0..100, in percent>
This setting allows to configure the hard limit of the number of data bytes
@ -5314,11 +5224,6 @@ tune.quic.fe.stream.data-ratio <0..100, in percent>
"tune.quic.be.stream.max-concurrent",
"tune.quic.fe.stream.max-concurrent"
tune.quic.frontend.stream-data-ratio <0..100, in percent> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.stream.max-concurrent <number>
tune.quic.fe.stream.max-concurrent <number>
On frontend side, this is used as the value for the advertised
@ -5362,11 +5267,6 @@ tune.quic.fe.stream.max-total <number>
The default value is 0 which implies no specific limit outside of the QUIC
protocol encoding limitation (2^60, more than a billion billion).
tune.quic.frontend.max-streams-bidi <number> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.stream.rxbuf <size>
tune.quic.fe.stream.rxbuf <size>
This setting is the hard limit for the number of data bytes in flight over a
@ -5384,11 +5284,6 @@ tune.quic.fe.stream.rxbuf <size>
"tune.quic.fe.stream.max-concurrent",
"tune.quic.be.stream.data-ratio", "tune.quic.fe.stream.data-ratio"
tune.quic.frontend.max-data-size <size> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.tx.pacing { on | off }
tune.quic.fe.tx.pacing { on | off }
Enables ('on') or disables ('off') pacing support for QUIC emission. By
@ -5400,11 +5295,6 @@ tune.quic.fe.tx.pacing { on | off }
See also the "quic-cc-algo" bind and server options.
tune.quic.disable-tx-pacing (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.be.tx.udp-gso { on | off }
tune.quic.fe.tx.udp-gso { on | off }
Enables ('on') or disables ('off') UDP GSO support for QUIC emission. By
@ -5413,11 +5303,6 @@ tune.quic.fe.tx.udp-gso { on | off }
be useful to disable it on developers suggestion when suspecting an issue on
emission.
tune.quic.disable-udp-gso (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.listen { on | off }
Disable QUIC transport protocol on the frontend side. All the QUIC listeners
will still be created, but they won't listen for incoming datagrams. Hence,
@ -5437,11 +5322,6 @@ tune.quic.mem.tx-max <size>
connection will always be able to use a window of at least 2 datagrams, so a
proper maxconn should be used in conjunction.
tune.quic.frontend.max-tx-mem <size> (deprecated)
This keyword has been deprecated in 3.3 and will be removed in 3.5. It is
part of the streamlining process apply on QUIC configuration. If used, this
setting will only be applied on frontend connections.
tune.quic.zero-copy-fwd-send { on | off }
Enables ('on') of disabled ('off') the zero-copy sends of data for the QUIC
multiplexer. It is enabled by default.
@ -6160,7 +6040,6 @@ default-server X - X X
default_backend X X X -
description - X X X
disabled X X X X
dispatch (deprecated) - - X X
email-alert from X X X X
email-alert level X X X X
email-alert mailers X X X X
@ -6269,7 +6148,6 @@ option tcp-smart-accept (*) X X X -
option tcp-smart-connect (*) X - X X
option tcpka X X X X
option tcplog X X X -
option transparent (deprecated) (*) X - X X
option use-small-buffers (*) X - X X
persist rdp-cookie X - X X
quic-initial X (!) X X -
@ -6331,7 +6209,6 @@ timeout server X - X X
timeout server-fin X - X X
timeout tarpit X X X X
timeout tunnel X - X X
transparent (deprecated) X - X X
unique-id-format X X X X
unique-id-header X X X -
use_backend - X X -
@ -6746,7 +6623,7 @@ balance url_param <param> [check_post]
might be a URL parameter list. This is probably not a concern with SGML
type message bodies.
See also : "dispatch", "cookie", "transparent", "hash-type".
See also : "cookie", "hash-type".
bind [<address>]:<port_range> [, ...] [param*]
@ -7527,54 +7404,6 @@ disabled
See also : "enabled", "force-be-switch"
dispatch <address>:<port> (deprecated)
Set a default server address
May be used in the following contexts: tcp, http
May be used in sections : defaults | frontend | listen | backend
no | no | yes | yes
Arguments :
<address> is the IPv4 address of the default server. Alternatively, a
resolvable hostname is supported, but this name will be resolved
during start-up.
<ports> is a mandatory port specification. All connections will be sent
to this port, and it is not permitted to use port offsets as is
possible with normal servers.
The "dispatch" keyword designates a default server for use when no other
server can take the connection. In the past it was used to forward non
persistent connections to an auxiliary load balancer. Due to its simple
syntax, it has also been used for simple TCP relays. It is recommended not to
use it for more clarity, and to use the "server" directive instead.
This keyword has been deprecated in 3.3 and will be removed in 3.5 due to
some internal limitations (no support for SSL nor idle connections etc).
Using it will emit a warning that may be silenced by enabling directive
"expose-deprecated-directives" in the global section.
The correct way to proceed without this directive is to simply declare a
server with the same address and port. If the "dispatch" directive was
mixed with other servers, then these servers should be configured with a
weight of zero in order never to be elected by the load balancing algorithm.
Example:
backend deprecated_setup
dispatch 192.168.100.100:80 # external load balancer's address
server s1 192.168.100.1:80 cookie S1 check
server s2 192.168.100.2:80 cookie S2 check
backend modern_setup
server external_lb 192.168.100.100:80
server s1 192.168.100.1:80 cookie S1 check weight 0
server s2 192.168.100.2:80 cookie S2 check weight 0
See also : "server"
dynamic-cookie-key <string>
Set the dynamic cookie secret key for a backend.
@ -12137,46 +11966,6 @@ option tcplog [clf]
See also : "option httplog", and section 8 about logging.
option transparent (deprecated)
no option transparent (deprecated)
Enable client-side transparent proxying
May be used in the following contexts: tcp, http
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
Arguments : none
This option was introduced in order to provide layer 7 persistence to layer 3
load balancers. The idea is to use the OS's ability to redirect an incoming
connection for a remote address to a local process (here HAProxy), and let
this process know what address was initially requested. When this option is
used, sessions without cookies will be forwarded to the original destination
IP address of the incoming request (which should match that of another
equipment), while requests with cookies will still be forwarded to the
appropriate server.
Note that contrary to a common belief, this option does NOT make HAProxy
present the client's IP to the server when establishing the connection.
As of 3.3, this option is now deprecated because it used to suffer from a
number of internal technical limitations. Using it will emit a warning, which
can be avoided if really needed via the "expose-deprecated-directives" global
keyword.
The correct approach is to declare a server on address 0.0.0.0, which will
take care of connecting to the expected destination address. A server will
also properly handle idle connections to the target servers.
Example:
# option transparent ## before 3.3
server transparent 0.0.0.0
See also: the "usesrc" argument of the "source" keyword, and the
"transparent" option of the "bind" keyword.
option use-small-buffers [ queue | l7-retries | check ]*
Enable support for small buffers for the given categories.
@ -12605,9 +12394,9 @@ server <name> <address>[:[port]] [param*]
address as the one from the client connection. This is useful in
transparent proxy architectures where the client's connection is
intercepted and HAProxy must forward to the original destination
address. This is more or less what the "transparent" keyword does
except that with a server it's possible to limit concurrency and
to report statistics. Optionally, an address family prefix may be
address. This is more or less what the old "transparent" keyword
did except that servers do not have the keyword's limitations
(ssl, queues etc). Optionally, an address family prefix may be
used before the address to force the family regardless of the
address format, which can be useful to specify a path to a unix
socket with no slash ('/'). Currently supported prefixes are :
@ -12969,7 +12758,9 @@ stats admin { if | unless } <cond>
matched.
The admin level allows to enable/disable servers from the web interface. By
default, statistics page is read-only for security reasons.
default, statistics page is read-only for security reasons. If "stats scope"
directives are set in the section, then only proxies designated by these
directives will accept state changes; access to other ones will be denied.
Currently, the POST request is limited to the buffer size minus the reserved
buffer space, which means that if the list of servers is too long, the
@ -13004,8 +12795,8 @@ stats admin { if | unless } <cond>
stats http-request auth unless AUTH
stats admin if AUTH_ADMIN
See also : "stats enable", "stats auth", "stats http-request", section 12.2
about userlists and section 7 about ACL usage.
See also : "stats enable", "stats auth", "stats http-request", "stats scope",
section 12.2 about userlists and section 7 about ACL usage.
ssl-f-use [<sslbindconf> ...]*
Assignate a certificate to the current frontend.
@ -13333,11 +13124,11 @@ stats scope { <name> | "." }
section in which the statement appears.
When this statement is specified, only the sections enumerated with this
statement will appear in the report. All other ones will be hidden. This
statement may appear as many times as needed if multiple sections need to be
reported. Please note that the name checking is performed as simple string
comparisons, and that it is never checked that a give section name really
exists.
statement will appear in the report. All other ones will be hidden, and
attempts to change their state in admin mode will be rejected. This statement
may appear as many times as needed if multiple sections need to be reported.
Please note that the name checking is performed as simple string comparisons,
and that it is never checked that a give section name really exists.
Though this statement alone is enough to enable statistics reporting, it is
recommended to set all other settings in order to avoid relying on default
@ -13361,7 +13152,8 @@ stats scope { <name> | "." }
stats uri /admin?stats
stats refresh 5s
See also : "stats auth", "stats enable", "stats realm", "stats uri"
See also : "stats auth", "stats enable", "stats realm", "stats uri" and
"stats admin"
stats show-desc [ <desc> ]
@ -15162,32 +14954,6 @@ timeout tunnel <timeout>
See also : "timeout client", "timeout client-fin", "timeout server".
transparent (deprecated)
Enable client-side transparent proxying
May be used in the following contexts: tcp, http
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
Arguments : none
This keyword was introduced in order to provide layer 7 persistence to layer
3 load balancers. The idea is to use the OS's ability to redirect an incoming
connection for a remote address to a local process (here HAProxy), and let
this process know what address was initially requested. When this option is
used, sessions without cookies will be forwarded to the original destination
IP address of the incoming request (which should match that of another
equipment), while requests with cookies will still be forwarded to the
appropriate server.
The "transparent" keyword is deprecated, use "option transparent" instead.
Note that contrary to a common belief, this option does NOT make HAProxy
present the client's IP to the server when establishing the connection.
See also: "option transparent"
unique-id-format <fmt>
Generate a unique ID for each request.
@ -15652,7 +15418,7 @@ cache-store <name>
See section 6.2 about cache setup.
cache-use <name>
cache-use <name> [no-early-hints]
Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft
- | - | - | - | - | X | - | -
@ -15661,6 +15427,12 @@ cache-use <name>
use a condition for both storage and delivering that's a good idea to put it
after this one.
When the named cache has "early-hints" enabled, lookups handled by this
rule may emit a 103 Early Hints response if the requested URL has known
"Link" hints in the cache. The optional "no-early-hints" keyword
suppresses 103 emission for requests handled by this rule, regardless of
the cache-level setting.
See section 6.2 about cache setup.
@ -16798,7 +16570,11 @@ set-retries <int> | <epxr>
Note that this action is only relevant on the backend side and thus this rule
is only available for the proxies with backend capability. It is also not
allowed in "defaults" sections.
allowed in "defaults" sections. When the action is used for a listener, it is
evaluated in the frontend context. So retries value is conserved only if
stream is not routed to a different backend, via a use-backend rule for
instance. Otherwise the default retries value of the selected backend will be
preset.
Example:
tcp-request content set-retries 3
@ -16886,7 +16662,11 @@ set-timeout { client | connect | queue | server | tarpit | tunnel } { <timeout>
the backend side and thus this rule is only available for the proxies with
backend capabilities. Likewise, client timeout is only relevant for frontend
side. Tarpit timeout is available to both sides. The timeout value must be
non-null to obtain the expected results.
non-null to obtain the expected results. When the action is used for a
listener, it is evaluated in the frontend context. So custom values for
backend-side timeouts are conserved only if stream is not routed to a
different backend, via a use-backend rule for instance. Otherwise the default
values of the selected backend will be preset.
Example:
http-request set-timeout tunnel 5s
@ -20469,6 +20249,33 @@ cache <name>
Declare a cache section, allocate a shared cache memory named <name>, the
size of cache is mandatory (see keyword "total-max-size" below).
early-hints <on/off/only> [ratio <integer>]
Enable or disable support for HTTP 103 Early Hints responses (see RFC 8297).
When enabled, the cache remembers relevant "Link" response headers (with a
"rel" parameter of preload, preconnect, dns-prefetch, modulepreload or
prefetch) seen on cached responses. When a subsequent "cache-use" lookup
cannot serve the full response from the cache but the URL's "Link" hints
are still known, the cache emits a 103 Early Hints response carrying those
hints before forwarding the request to the backend, giving the client a
head start on fetching subresources while the backend produces the body.
Individual "cache-use" rules may opt out of this behavior with the
"no-early-hints" keyword. The default value is off (disabled).
When "early-hints" is set to "only", no responses are stored in the cache.
Instead, if a backend response contains relevant "Link" headers as defined
above, the cache stores those in order to emit a 103 Early Hints response if
that same URL is accessed later. Since the response body is never stored in
this mode, the "max-object-size" limit does not apply: hints are extracted
and stored regardless of the response's size.
The optional "ratio" argument sets the percentage of total cache blocks
that may be reserved for hints-only entries (1 to 99, default 25). Higher
values keep hints alive longer at the cost of less space for full responses;
lower values prioritize full responses. This argument only makes sense when
"early-hints" is set to on, and is ignored otherwise. In "only" mode every
stored entry is a hints entry, so the whole cache is available to them and
this ratio has no practical effect.
max-age <seconds>
Define the maximum expiration duration. The expiration is set as the lowest
value between the s-maxage or max-age (in this order) directive in the
@ -24524,6 +24331,7 @@ bc_srv_queue integer
be_id integer
be_name string
be_connect_timeout integer
be_max_retries integer
be_queue_timeout integer
be_server_timeout integer
be_tarpit_timeout integer
@ -24532,6 +24340,7 @@ bytes_in integer
bytes_out integer
cur_connect_timeout integer
cur_client_timeout integer
cur_max_retries integer
cur_queue_timeout integer
cur_server_timeout integer
cur_tarpit_timeout integer
@ -24868,6 +24677,11 @@ be_connect_timeout : integer
current backend. This timeout can be overwritten by a "set-timeout" rule. See
also the "cur_connect_timeout".
be_max_retries : integer
Returns the configuration value for the connection retries of the current
backend. This value can be overwritten by a "set-retries" rule. See also the
"cur_max_retries".
be_name : string
Returns a string containing the current backend's name. It can be used in
frontends with responses to check which backend processed the request. If
@ -24911,6 +24725,11 @@ cur_client_timeout : integer
In the default case, this will be equal to fe_client_timeout unless a
"set-timeout" rule has been applied. See also "fe_client_timeout".
cur_max_retries : integer
Returns the currently applied connect retries for the stream.
In the default case, this will be equal to be_max_retries unless a
"set-retries" rule has been applied. See also "be_max_retries".
cur_queue_timeout : integer
Returns the currently applied queue timeout in millisecond for the stream.
In the default case, this will be equal to be_queue_timeout unless a
@ -30693,41 +30512,9 @@ See also: "use-fcgi-app", section 9.2 about the compression filter, section 9.4
9.6. OpenTracing
----------------
The OpenTracing filter adds native support for using distributed tracing in
HAProxy. This is enabled by sending an OpenTracing compliant request to one
of the supported tracers such as Datadog, Jaeger, Lightstep and Zipkin tracers.
Please note: tracers are not listed by any preference, but alphabetically.
This feature is only enabled when HAProxy was built with USE_OT=1.
The OpenTracing filter activation is done explicitly by specifying it in the
HAProxy configuration. If this is not done, the OpenTracing filter in no way
participates in the work of HAProxy.
filter opentracing [id <id>] config <file>
Arguments :
<id> is the OpenTracing filter id that will be used to find the
right scope in the configuration file. If no filter id is
specified, 'ot-filter' is used as default. If scope is not
specified in the configuration file, it applies to all defined
OpenTracing filters.
<file> is the path of the OpenTracing configuration file. The same
file can contain configurations for multiple OpenTracing
filters simultaneously. In that case we do not need to define
scope so the same configuration applies to all filters or each
filter must have its own scope defined.
More detailed documentation related to the operation, configuration and use
of the filter can be found in the addons/ot directory.
Note: The OpenTracing filter shouldn't be used for new designs as OpenTracing
itself is no longer maintained nor supported by its authors. As such
OpenTracing will be deprecated in 3.3 and removed in 3.5. A replacement
filter based on OpenTelemetry is available since 3.4 with complete build
instructions currently at:
The OpenTracing filter was removed in version 3.5. A replacement filter based
on OpenTelemetry is available since 3.4 with complete build instructions
currently at:
https://github.com/haproxytech/haproxy-opentelemetry/

146
doc/haload.txt Normal file
View file

@ -0,0 +1,146 @@
------
HALoad
------
HAProxy's dummy HTTP
client for benchmarks
1. Background
-------------
HALoad is a lightweight, multi-threaded traffic generator designed to benchmark
HTTP infrastructures under heavy loads. Built directly onto HAProxy's highly
scalable core architecture, it shares its parent engine's efficient handling of
connections. This framework allows the tool to generate high-volume traffic
across all standard application layers, including HTTP/1, HTTP/2, and HTTP/3
(QUIC), over either cleartext or secured TLS connections.
The primary design goal is to provide a modernized alternative to legacy tools
like h1load, extending benchmarking capabilities to newer protocols. Notably,
HALoad introduces the concept of users (-u), a feature completely absent
from h1load. Here, a "user" strictly represents an independent, concurrent
HTTP client task. Under this architecture, each simulated client will
instantiate as many backend server connections as there are target URLs
specified on the command line.
HALoad does not require any configuration file. Instead, the configuration is
dynamically derived from basic command line inputs. This ensures immediate
usability for test operators while retaining the ability to test complex,
multi-protocol setups.
2. Compilation
--------------
The compilation process mirrors standard HAProxy builds, specifying "haload"
as the compilation target via the command line:
$ make -j $(nproc) TARGET=linux-glibc haload
To enable encrypted communication layers (TLS/SSL):
$ make TARGET=linux-glibc USE_OPENSSL=1 haload
For advanced HTTP/3 over QUIC load testing:
$ make -j $(nproc) TARGET=linux-glibc USE_OPENSSL=1 USE_QUIC=1 haload
Because HALoad shares the code of the main HAProxy binary, it natively
inherits all standard HAProxy compiler flags, optimizations, and build
targets.
3. Execution
------------
HALoad displays its usage when run without argument or wrong arguments:
Usage : haload [opts] [URL]
where <opts> may be any combination of:
-d <time> test duration in seconds (0)
-e stop upon first connection error
-h(0|1|2|2c|3) use h0 (hq-interop for QUIC), h1, h2, h2c or h3
(QUIC/TCP) protocols (*)
-(0|1|2|2c|3) same as above (*)
-l enable long output format; double for raw values
-m <streams> maximum concurrent streams (1)
-n <reqs> maximum total requests (-1)
-r <reqs> number of requests per connection (-1)
-s <time> soft start: time in sec to reach 100% load
-t <threads> number of threads
-u <users> number of users (1)
-w <time> I/O timeout in milliseconds (10000)
-C dump the configuration and exit
-H "foo:bar" add this header name and value
-I use HEAD instead of GET
-v shows version
--defaults <str> add a string to default section
--global <str> add a string to global section
--server <opts> set server <opt> options as defined for "server"
haproxy keyword
--show-status-codes show HTTP status codes distribution
--traces enable the traces for all the HTTP protocols
SSL options:
--tls-ciphers <ciphers> for TLS1.2 and below (*)
--tls-ciphersuites <ciphers> for TLS1.3 and above (*)
--tls-curves <curves> (*)
URL format:
(http://|https://|quic://)<addr>:<port>/<path>
Note: Options marked with an asterisk (*) are positional and MUST be placed
BEFORE the URLs they are intended to affect.
At startup, HALoad dynamically generates an HAProxy configuration in memory.
Options like --global and --defaults allow raw configuration lines to be added
directly to the "global" and "defaults" sections, while --server appends
options to the backend server. These parameters support standard escaped
notation (e.g., '\n' or '\t') to insert multi-line statements in a single
argument en ligne de commande.
Examples:
# 30-second test using 4 threads simulating 10 distinct users
$ ./haload -t 4 -u 10 -d 30 http://127.0.0.1:8888/
# HTTP/3 test limiting concurrent streams to 3 per connection (-m3)
$ ./haload -m3 quic://127.0.0.1:8889/
# Dump the generated memory configuration and exit (-C)
$ ./haload https://127.0.0.1:4443 -C
global
tune.memory.hot-size 3145728
ssl-server-verify none
tune.h2.be.max-concurrent-streams 1
tune.quic.be.stream.max-concurrent 1
# Dump the configuration and exit (-C) to check -m3 effect
$ ./haload https://127.0.0.1:4443 -m3 -C
global
tune.memory.hot-size 3145728
ssl-server-verify none
tune.h2.be.max-concurrent-streams 3
tune.quic.be.stream.max-concurrent 3
# Add a custom directive into defaults, dump the configuration and exit (-C)
$ ./haload https://127.0.0.1:4443 --defaults "timeout connect 5s" -C
global
tune.memory.hot-size 3145728
ssl-server-verify none
tune.h2.be.max-concurrent-streams 1
tune.quic.be.stream.max-concurrent 1
defaults
timeout connect 5s
4. Limitations
--------------
When using HTTP/3 (QUIC or QMUX) protocols with the maximum concurrent streams
option (-m) enabled, HAProxy's connection layer currently does not enforce or
respect the strict limit on the number of open connections usually dictated by
the concurrent users option (-u).
For example, when targeting a single URL, specifying "-u 10 -m 5" might lead
to opening more than 10 concurrent connections to the target server under
heavy load, instead of strictly capping the total connections to the requested
number of users.

View file

@ -33,7 +33,7 @@ target to provide on the "make" command line as follows:
HATerm supports HTTPS/SSL/TCP:
$ make TARGET=linux-glibc USE_OPENSSL=1
$ make TARGET=linux-glibc USE_OPENSSL=1 haterm
It also supports QUIC:

View file

@ -1367,6 +1367,16 @@ Here is the list of static fields using the proxy statistics domain:
since the worker process started
112. proto [L...]: protocol
113. priv_idle_cur [...S]: current number of private idle connections
114. reqbin [LFBS]: total number of request bytes received since the worker
process started
115. reqbout [LFBS]: total number of request bytes sent since the worker
process started
116. resbin [LFBS]: total number of response bytes received since the worker
process started
117. resbout [LFBS]: total number of response bytes sent since the worker
process started
118. cache_hint_hits [.FB.]: cumulative number of cache hits having served
early hints
For all other statistics domains, the presence or the order of the fields are
not guaranteed. In this case, the header line should always be used to parse
@ -2148,8 +2158,7 @@ del backend <name>
There is additional restrictions which prevent backend removal. First, a
backend cannot be removed if it is explicitly referenced by config elements,
for example via a use_backend rule or in sample expressions. Some proxies
options are also incompatible with runtime deletion. Currently, this is the
case when deprecated dispatch or option transparent are used. Also, a backend
options are also incompatible with runtime deletion. Currently, a backend
cannot be removed if there is a stick-table declared in it. Finally, it is
impossible for now to remove a backend if QUIC servers were present in it.
@ -2701,6 +2710,46 @@ set server <backend>/<server> ssl [ on | off ] (deprecated)
This command is deprecated, create a new server dynamically with or without
SSL instead, using the "add server" command.
set server <backend>/<server> name <newname>
Change the name of a server at runtime.
Two preconditions are enforced:
- the server must be administratively in maintenance mode (set first
via "set server <b>/<s> state maint" or "disable server <b>/<s>");
- the server's name must not be statically referenced by a
"use-server" rule, a "track" directive, or a sample-fetch argument
of type ARGT_SRV. Such references are recorded at configuration
load time and renaming the target would leave the running state
inconsistent with the configuration text.
Like "add server" and "del server", this command is not gated by a
per-backend opt-in directive: availability is determined entirely by
the server's runtime properties.
The new name must be syntactically valid (non-empty, must pass
invalid_char(), i.e. only [A-Za-z0-9_:.-]) and must be unique among
all servers in the backend. The change is effective immediately for
all runtime operations, stats, and logs, but is not persisted to the
configuration file and will be lost on reload. Some features that
rely on static server names (such as stick-tables or external
monitoring) may not recognize the new name until restart.
A successful rename publishes an EVENT_HDL_SUB_SERVER_NAME event
carrying the old and new names, which Lua scripts and other event
consumers can subscribe to.
There is no strict requirement that no sessions are active before
renaming. However, if there are active sessions, log entries for
those sessions may use either the old or new name, depending on when
the rename occurs relative to logging. This may result in split or
inconsistent logs for in-flight requests. If strict log consistency
is required, it is the user's responsibility to ensure all sessions
have finished before renaming (e.g. by draining via maintenance mode
and waiting for all connections to close).
Example:
set server myapp/s1 name blue
set severity-output [ none | number | string ]
Change the severity output format of the stats socket connected to for the
duration of the current session.
@ -3108,7 +3157,7 @@ show events [<sink>] [-w] [-n] [-0]
delimited by a line feed character ('\n' or 10 or 0x0A). It is possible to
change this to the NUL character ('\0' or 0) by passing the "-0" argument.
show fd [-!plcfbsd]* [[<tgid>]/[<fd>] | <fd>]
show fd [-!plcfbs]* [[<tgid>]/[<fd>] | <fd>]
Dump the list of either all open file descriptors or just the one number <fd>
if specified. The form "<tgid>/<fd>" is also accepted, where either side may
be empty as a wildcard ("/<fd>" for fd <fd> across thread groups, "<tgid>/"
@ -3118,30 +3167,28 @@ show fd [-!plcfbsd]* [[<tgid>]/[<fd>] | <fd>]
types. When '-' or '!' are encountered, the selection is inverted for the
following characters in the same argument. The inversion is reset before each
argument word delimited by white spaces. Selectable FD types include 'p' for
pipes, 'l' for listeners, 'c' for connections (any type), 'f' for frontend
connections, 'b' for backend connections (any type), 's' for connections to
servers, 'd' for connections to the "dispatch" address or the backend's
transparent address. With this, 'b' is a shortcut for 'sd' and 'c' for 'fb' or
'fsd'. 'c!f' is equivalent to 'b' ("any connections except frontend
connections" are indeed backend connections). This is only aimed at developers
who need to observe internal states in order to debug complex issues such as
abnormal CPU usages. One fd is reported per lines, and for each of them, its
state in the poller using upper case letters for enabled flags and lower case
for disabled flags, using "P" for "polled", "R" for "ready", "A" for "active",
the events status using "H" for "hangup", "E" for "error", "O" for "output",
"P" for "priority" and "I" for "input", a few other flags like "N" for "new"
(just added into the fd cache), "U" for "updated" (received an update in the
fd cache), "L" for "linger_risk", "C" for "cloned", then the cached entry
pipes, 'l' for listeners, 'c' for connections (any type, shortcut for 'fb'),
'f' for frontend connections, 'b' or 's' for backend connections to servers.
'c!f' is equivalent to 'b' ("any connections except frontend connections" are
indeed backend connections). This is only aimed at developers who need to
observe internal states in order to debug complex issues such as abnormal CPU
usages. One fd is reported per lines, and for each of them, its state in the
poller using upper case letters for enabled flags and lower case for disabled
flags, using "P" for "polled", "R" for "ready", "A" for "active", the events
status using "H" for "hangup", "E" for "error", "O" for "output", "P" for
"priority" and "I" for "input", a few other flags like "N" for "new" (just
added into the fd cache), "U" for "updated" (received an update in the fd
cache), "L" for "linger_risk", "C" for "cloned", then the cached entry
position, the pointer to the internal owner, the pointer to the I/O callback
and its name when known. When the owner is a connection, the connection flags,
and the target are reported (frontend, proxy or server). When the owner is a
listener, the listener's state and its frontend are reported. There is no
point in using this command without a good knowledge of the internals. It's
worth noting that the output format may evolve over time so this output must
not be parsed by tools designed to be durable. Some internal structure states
may look suspicious to the function listing them, in this case the output line
will be suffixed with an exclamation mark ('!'). This may help find a starting
point when trying to diagnose an incident.
its name when known. When the owner is a connection, the connection and
flags, and the target are reported (frontend, proxy or server). When the
owner is a listener, the listener's state and its frontend are reported.
There is no point in using this command without a good knowledge of the
internals. It's worth noting that the output format may evolve over time so
this output must not be parsed by tools designed to be durable. Some internal
structure states may look suspicious to the function listing them, in this
case the output line will be suffixed with an exclamation mark ('!'). This
may help find a starting point when trying to diagnose an incident.
show info [typed|json] [desc] [float]
Dump info about haproxy status on current process. If "typed" is passed as an

View file

@ -83,7 +83,6 @@ enum {
ARGC_UBK, /* use_backend message */
ARGC_USRV, /* use-server message */
ARGC_HERR, /* http-error */
ARGC_OT, /* opentracing scope args */
ARGC_OPT, /* option directive */
ARGC_TCO, /* tcp-request connection expression */
ARGC_TSE, /* tcp-request session expression */

View file

@ -51,6 +51,7 @@ struct buffer *get_trash_chunk(void);
struct buffer *get_large_trash_chunk(void);
struct buffer *get_small_trash_chunk(void);
struct buffer *get_trash_chunk_sz(size_t size);
struct buffer *get_best_trash_chunk(const struct buffer *buf, size_t size);
struct buffer *get_larger_trash_chunk(struct buffer *chunk);
int init_trash_buffers(int first);
@ -172,6 +173,21 @@ static forceinline struct buffer *alloc_trash_chunk_sz(size_t size)
else
return NULL;
}
/* Returns a trash chunk accordingly to the requested size and never larger
* that the buffer <buf>. So if <buf> is a large buffer,
* alloc_trash_chunk_sz() function is called. Otherwise, if the size is
* smaller enough, a regular buffer is allocated. If <size> is too big and
* <buf> is not a large buffer, NULL is returned.
*/
static forceinline struct buffer *alloc_best_trash_chunk(const struct buffer *buf, size_t size)
{
if (pool_head_large_trash && buf->size == pool_head_large_trash->size)
return alloc_trash_chunk_sz(size);
else if (size <= pool_head_trash->size)
return alloc_trash_chunk();
else
return NULL;
}
/*
* free a trash chunk allocated by alloc_trash_chunk(). NOP on NULL.

View file

@ -591,4 +591,15 @@
# endif
#endif
/* Eats all arguments ignoring their types and contents. This is useful
* to avoid "foo declared but not used" warnings. A first non-empty arg
* is required by the function, but it's inserted by the macro.
*/
#define __eat_all_args(...) ___eat_all_args(0, __VA_ARGS__)
static inline __attribute__((always_inline)) void ___eat_all_args(int ignore, ...)
{
(void)ignore;
}
#endif /* _HAPROXY_COMPILER_H */

View file

@ -734,7 +734,7 @@ static inline struct proxy *conn_get_proxy(const struct connection *conn)
if (s)
return s->proxy;
return objt_proxy(conn->target);
return NULL;
}
/* unconditionally retrieves the ssl_sock_ctx for this connection. Prefer using

Some files were not shown because too many files have changed in this diff Show more