Compare commits

..

No commits in common. "master" and "v3.5-dev1" have entirely different histories.

259 changed files with 14455 additions and 8755 deletions

2
.github/matrix.py vendored
View file

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

View file

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

View file

@ -65,6 +65,13 @@ jobs:
path: '~/opt/' path: '~/opt/'
key: ssl-${{ steps.generate-cache-key.outputs.key }} 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 - name: Add i386 architecture
if: ${{ matrix.CC == 'i686-linux-gnu-gcc' }} if: ${{ matrix.CC == 'i686-linux-gnu-gcc' }}
run: sudo dpkg --add-architecture i386 run: sudo dpkg --add-architecture i386
@ -92,6 +99,10 @@ jobs:
- name: Install SSL ${{ matrix.ssl }} - name: Install SSL ${{ matrix.ssl }}
if: ${{ matrix.ssl && matrix.ssl != 'stock' && steps.cache_ssl.outputs.cache-hit != 'true' }} if: ${{ matrix.ssl && matrix.ssl != 'stock' && steps.cache_ssl.outputs.cache-hit != 'true' }}
run: env ${{ matrix.ssl }} scripts/build-ssl.sh 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 - name: Build WURFL
if: ${{ contains(matrix.FLAGS, 'USE_WURFL=1') }} if: ${{ contains(matrix.FLAGS, 'USE_WURFL=1') }}
run: make -C addons/wurfl/dummy run: make -C addons/wurfl/dummy

116
CHANGELOG
View file

@ -1,122 +1,6 @@
ChangeLog : 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 2026/06/25 : 3.5-dev1
- BUG/MEDIUM: check: Skip tcpcheck post-config for external checks - BUG/MEDIUM: check: Skip tcpcheck post-config for external checks
- BUG/MEDIUM: check: Ignore small-buffer option when starting an external check - 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 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 selected target. When facing build errors, if you know that your system is a
bit special or particularly old, start from TARGET=generic or TARGET=tiny; it bit special or particularly old, start from TARGET=generic, it is easier to
is easier to start from there and fix the remaining issues than trying to start from there and fix the remaining issues than trying to degrade another
degrade another target. Common issues may include: target. Common issues may include:
- clock_gettime() not found - clock_gettime() not found
=> your system needs USE_RT=1 => your system needs USE_RT=1
@ -579,7 +579,6 @@ and assign it to the TARGET variable :
- haiku for Haiku - haiku for Haiku
- generic for any other OS or version. - generic for any other OS or version.
- custom to manually adjust every setting - custom to manually adjust every setting
- tiny to turn all default options off by default
Example: Example:
$ make -j $(nproc) TARGET=linux-glibc $ make -j $(nproc) TARGET=linux-glibc
@ -604,14 +603,8 @@ 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 can even be disabled by setting them to an empty string or a zero. For
example : example :
$ gmake TARGET=small USE_POLL="" USE_SLZ=0 TARGET_CFLAGS=-fomit-frame-pointer $ gmake TARGET=tiny 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 5.2) Adding extra CFLAGS for compiling
-------------------------------------- --------------------------------------

View file

@ -60,14 +60,12 @@
# USE_WURFL : enable WURFL detection library from Scientiamobile # USE_WURFL : enable WURFL detection library from Scientiamobile
# USE_OBSOLETE_LINKER : use when the linker fails to emit __start_init/__stop_init # 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_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_MEMORY_PROFILING : enable the memory profiler. Linux-glibc only.
# USE_LIBATOMIC : force to link with/without libatomic. Automatic. # USE_LIBATOMIC : force to link with/without libatomic. Automatic.
# USE_PTHREAD_EMULATION : replace pthread's rwlocks with ours # 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_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_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 # 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 # "USE_xxx=" (empty string). The list of enabled and disabled options for a
@ -127,6 +125,11 @@
# LUA_INC : force the include path to lua # LUA_INC : force the include path to lua
# LUA_LIB_NAME : force the lib name (or automatically evaluated, by order of # LUA_LIB_NAME : force the lib name (or automatically evaluated, by order of
# priority: lua5.5, lua55, lua5.4, lua54, lua5.3, lua53, lua). # 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. # IGNOREGIT : ignore GIT commit versions if set.
# VERSION : force haproxy version reporting. # VERSION : force haproxy version reporting.
# SUBVERS : add a sub-version (eg: platform, model, ...). # SUBVERS : add a sub-version (eg: platform, model, ...).
@ -157,7 +160,7 @@ DOCDIR = $(PREFIX)/doc/haproxy
# following list (use the default "generic" if uncertain) : # following list (use the default "generic" if uncertain) :
# linux-glibc, linux-glibc-legacy, linux-musl, solaris, freebsd, freebsd-glibc, # linux-glibc, linux-glibc-legacy, linux-musl, solaris, freebsd, freebsd-glibc,
# dragonfly, openbsd, netbsd, cygwin, haiku, aix51, aix52, aix72-gcc, osx, generic, # dragonfly, openbsd, netbsd, cygwin, haiku, aix51, aix52, aix72-gcc, osx, generic,
# custom, tiny # custom
TARGET = TARGET =
#### No longer used #### No longer used
@ -341,12 +344,12 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER USE_POLL \
USE_TPROXY USE_LINUX_TPROXY USE_LINUX_CAP \ USE_TPROXY USE_LINUX_TPROXY USE_LINUX_CAP \
USE_LINUX_SPLICE USE_LIBCRYPT USE_CRYPT_H USE_ENGINE \ USE_LINUX_SPLICE USE_LIBCRYPT USE_CRYPT_H USE_ENGINE \
USE_GETADDRINFO USE_OPENSSL USE_OPENSSL_WOLFSSL USE_OPENSSL_AWSLC \ USE_GETADDRINFO USE_OPENSSL USE_OPENSSL_WOLFSSL USE_OPENSSL_AWSLC \
USE_ECH USE_TRACE USE_FCGI USE_H2 USE_SPOE \ USE_ECH USE_TRACE \
USE_SSL USE_LUA USE_ACCEPT4 USE_CLOSEFROM USE_ZLIB USE_SLZ \ 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_CPU_AFFINITY USE_TFO USE_NS USE_DL USE_RT USE_LIBATOMIC \
USE_MATH USE_DEVICEATLAS USE_51DEGREES \ USE_MATH USE_DEVICEATLAS USE_51DEGREES \
USE_WURFL USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL \ USE_WURFL USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL \
USE_THREAD_DUMP USE_EVPORTS USE_QUIC USE_PROMEX \ USE_THREAD_DUMP USE_EVPORTS USE_OT USE_QUIC USE_PROMEX \
USE_MEMORY_PROFILING USE_SHM_OPEN \ USE_MEMORY_PROFILING USE_SHM_OPEN \
USE_STATIC_PCRE USE_STATIC_PCRE2 \ USE_STATIC_PCRE USE_STATIC_PCRE2 \
USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT \ USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT \
@ -367,15 +370,6 @@ USE_POLL = default
# traces are always enabled # traces are always enabled
USE_TRACE = default 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="" # SLZ is always supported unless explicitly disabled by passing USE_SLZ=""
# or disabled by enabling ZLIB using USE_ZLIB=1 # or disabled by enabling ZLIB using USE_ZLIB=1
ifeq ($(USE_ZLIB:0=),) ifeq ($(USE_ZLIB:0=),)
@ -387,13 +381,6 @@ ifeq ($(TARGET),generic)
set_target_defaults = $(call default_opts,USE_POLL USE_TPROXY) set_target_defaults = $(call default_opts,USE_POLL USE_TPROXY)
endif 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 # Haiku
ifeq ($(TARGET),haiku) ifeq ($(TARGET),haiku)
TARGET_LDFLAGS = -lnetwork TARGET_LDFLAGS = -lnetwork
@ -586,18 +573,6 @@ ifneq ($(USE_ZLIB:0=),)
ZLIB_LDFLAGS = $(if $(ZLIB_LIB),-L$(ZLIB_LIB)) -lz ZLIB_LDFLAGS = $(if $(ZLIB_LIB),-L$(ZLIB_LIB)) -lz
endif 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=),) ifneq ($(USE_SLZ:0=),)
OPTIONS_OBJS += src/slz.o OPTIONS_OBJS += src/slz.o
endif endif
@ -660,7 +635,6 @@ endif
ifneq ($(USE_OPENSSL_AWSLC:0=),) ifneq ($(USE_OPENSSL_AWSLC:0=),)
# always automatically set USE_OPENSSL # always automatically set USE_OPENSSL
USE_OPENSSL := $(if $(USE_OPENSSL:0=),$(USE_OPENSSL:0=),implicit) USE_OPENSSL := $(if $(USE_OPENSSL:0=),$(USE_OPENSSL:0=),implicit)
OPTIONS_OBJS += src/fips.o
endif endif
# This is for any variant of the OpenSSL API. By default it uses OpenSSL. # This is for any variant of the OpenSSL API. By default it uses OpenSSL.
@ -674,10 +648,7 @@ ifneq ($(USE_OPENSSL:0=),)
OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o \ 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_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/ssl_utils.o src/jwt.o src/ssl_clienthello.o src/jws.o src/acme.o \
src/acme_resolvers.o src/jwe.o src/acme_resolvers.o src/ssl_trace.o src/jwe.o
ifneq ($(USE_TRACE:0=),)
OPTIONS_OBJS += src/ssl_trace.o
endif
endif endif
ifneq ($(USE_ENGINE:0=),) ifneq ($(USE_ENGINE:0=),)
@ -838,6 +809,11 @@ ifneq ($(USE_LINUX_CAP:0=),)
OPTIONS_OBJS += src/linuxcap.o OPTIONS_OBJS += src/linuxcap.o
endif 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),) ifneq ($(EXTRA_MAKE),)
include $(addsuffix /Makefile.mk,$(EXTRA_MAKE)) include $(addsuffix /Makefile.mk,$(EXTRA_MAKE))
endif endif
@ -937,31 +913,30 @@ endif # TARGET
OBJS = OBJS =
HATERM_OBJS = HATERM_OBJS =
HALOAD_OBJS =
ifneq ($(EXTRA_OBJS),) ifneq ($(EXTRA_OBJS),)
OBJS += $(EXTRA_OBJS) OBJS += $(EXTRA_OBJS)
endif endif
OBJS += src/mux_h1.o src/log.o \ OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.o \
src/server.o src/stream.o src/tcpcheck.o src/http_ana.o \ src/server.o src/stream.o src/tcpcheck.o src/http_ana.o \
src/stick_table.o src/tools.o src/sample.o \ src/stick_table.o src/tools.o src/mux_spop.o src/sample.o \
src/activity.o src/cfgparse.o src/peers.o src/cli.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/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/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/check.o src/stats-html.o src/haproxy.o src/listener.o \
src/applet.o src/pattern.o src/cfgparse-listen.o \ src/applet.o src/pattern.o src/cfgparse-listen.o \
src/cebis_tree.o src/http_ext.o \ src/flt_spoe.o src/cebis_tree.o src/http_ext.o \
src/http_act.o src/http_fetch.o src/cebs_tree.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/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/cebb_tree.o src/vars.o src/event_hdl.o src/tcp_rules.o \
src/stats-proxy.o src/pool.o src/stats.o \ src/trace.o src/stats-proxy.o src/pool.o src/stats.o \
src/cfgparse-global.o src/filters.o src/mux_pt.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/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/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/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/server_state.o src/proto_rhttp.o src/flt_trace.o src/fd.o \
src/task.o src/map.o src/mworker.o \ src/task.o src/map.o src/fcgi-app.o src/h2.o src/mworker.o \
src/tcp_sample.o src/mjson.o src/h1_htx.o src/tcp_act.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/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 \ src/http_rules.o src/http.o src/channel.o src/proto_tcp.o \
@ -991,13 +966,7 @@ ifneq ($(TRACE),)
OBJS += src/calltrace.o OBJS += src/calltrace.o
endif endif
ifneq ($(USE_TRACE:0=),) HATERM_OBJS += $(OBJS) src/haterm_init.o
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. # Used only for forced dependency checking. May be cleared during development.
INCLUDES = $(wildcard include/*/*.h) INCLUDES = $(wildcard include/*/*.h)
@ -1049,9 +1018,6 @@ haproxy: $(OPTIONS_OBJS) $(OBJS)
haterm: $(OPTIONS_OBJS) $(HATERM_OBJS) haterm: $(OPTIONS_OBJS) $(HATERM_OBJS)
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
haload: $(OPTIONS_OBJS) $(HALOAD_OBJS)
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
objsize: haproxy objsize: haproxy
$(Q)objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort $(Q)objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort

View file

@ -41,7 +41,6 @@ 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 - [BRANCHES](BRANCHES) to understand the project's life cycle and what version to use
- [LICENSE](LICENSE) for the project's license - [LICENSE](LICENSE) for the project's license
- [CONTRIBUTING](CONTRIBUTING) for the process to follow to submit contributions - [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: The more detailed documentation is located into the doc/ directory:

View file

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

View file

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

1
addons/ot/AUTHORS Normal file
View file

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

1
addons/ot/MAINTAINERS Normal file
View file

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

73
addons/ot/Makefile Normal file
View file

@ -0,0 +1,73 @@
# 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)

799
addons/ot/README Normal file
View file

@ -0,0 +1,799 @@
-----------------------------------------
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%).

298
addons/ot/README-func Normal file
View file

@ -0,0 +1,298 @@
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() {
}
}

25
addons/ot/README-pool Normal file
View file

@ -0,0 +1,25 @@
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%).

50
addons/ot/include/cli.h Normal file
View file

@ -0,0 +1,50 @@
/***
* 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
*/

228
addons/ot/include/conf.h Normal file
View file

@ -0,0 +1,228 @@
/***
* 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

@ -0,0 +1,46 @@
/***
* 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
*/

104
addons/ot/include/debug.h Normal file
View file

@ -0,0 +1,104 @@
/***
* 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
*/

107
addons/ot/include/define.h Normal file
View file

@ -0,0 +1,107 @@
/***
* 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
*/

120
addons/ot/include/event.h Normal file
View file

@ -0,0 +1,120 @@
/***
* 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

@ -0,0 +1,68 @@
/***
* 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
*/

61
addons/ot/include/group.h Normal file
View file

@ -0,0 +1,61 @@
/***
* 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
*/

41
addons/ot/include/http.h Normal file
View file

@ -0,0 +1,41 @@
/***
* 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

@ -0,0 +1,66 @@
/***
* 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

@ -0,0 +1,86 @@
/***
* 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
*/

172
addons/ot/include/parser.h Normal file
View file

@ -0,0 +1,172 @@
/***
* 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
*/

39
addons/ot/include/pool.h Normal file
View file

@ -0,0 +1,39 @@
/***
* 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
*/

126
addons/ot/include/scope.h Normal file
View file

@ -0,0 +1,126 @@
/***
* 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
*/

109
addons/ot/include/util.h Normal file
View file

@ -0,0 +1,109 @@
/***
* 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
*/

55
addons/ot/include/vars.h Normal file
View file

@ -0,0 +1,55 @@
/***
* 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
*/

397
addons/ot/src/cli.c Normal file
View file

@ -0,0 +1,397 @@
/***
* 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
*/

764
addons/ot/src/conf.c Normal file
View file

@ -0,0 +1,764 @@
/***
* 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
*/

338
addons/ot/src/event.c Normal file
View file

@ -0,0 +1,338 @@
/***
* 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
*/

1182
addons/ot/src/filter.c Normal file

File diff suppressed because it is too large Load diff

354
addons/ot/src/group.c Normal file
View file

@ -0,0 +1,354 @@
/***
* 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
*/

312
addons/ot/src/http.c Normal file
View file

@ -0,0 +1,312 @@
/***
* 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
*/

1067
addons/ot/src/opentracing.c Normal file

File diff suppressed because it is too large Load diff

1230
addons/ot/src/parser.c Normal file

File diff suppressed because it is too large Load diff

223
addons/ot/src/pool.c Normal file
View file

@ -0,0 +1,223 @@
/***
* 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
*/

634
addons/ot/src/scope.c Normal file
View file

@ -0,0 +1,634 @@
/***
* 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
*/

815
addons/ot/src/util.c Normal file
View file

@ -0,0 +1,815 @@
/***
* 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
*/

841
addons/ot/src/vars.c Normal file
View file

@ -0,0 +1,841 @@
/***
* 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

@ -0,0 +1,111 @@
--- 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

@ -0,0 +1,111 @@
--- 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

@ -0,0 +1,111 @@
--- 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

@ -0,0 +1,111 @@
--- 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

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

View file

@ -0,0 +1,34 @@
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

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

View file

@ -0,0 +1,37 @@
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

62
addons/ot/test/be/ot.cfg Normal file
View file

@ -0,0 +1,62 @@
[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

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

View file

@ -0,0 +1,34 @@
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

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

View file

@ -0,0 +1,36 @@
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

83
addons/ot/test/cmp/ot.cfg Normal file
View file

@ -0,0 +1,83 @@
[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

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

View file

@ -0,0 +1,34 @@
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

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

View file

@ -0,0 +1,38 @@
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

197
addons/ot/test/ctx/ot.cfg Normal file
View file

@ -0,0 +1,197 @@
[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

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

View file

@ -0,0 +1,34 @@
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

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

View file

@ -0,0 +1,30 @@
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

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

View file

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

View file

@ -0,0 +1,34 @@
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

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

View file

@ -0,0 +1,37 @@
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

74
addons/ot/test/fe/ot.cfg Normal file
View file

@ -0,0 +1,74 @@
[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

5
addons/ot/test/func-stat.sh Executable file
View file

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

View file

@ -0,0 +1,45 @@
#!/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

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

13
addons/ot/test/run-cmp.sh Executable file
View file

@ -0,0 +1,13 @@
#!/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

13
addons/ot/test/run-ctx.sh Executable file
View file

@ -0,0 +1,13 @@
#!/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

47
addons/ot/test/run-fe-be.sh Executable file
View file

@ -0,0 +1,47 @@
#!/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

13
addons/ot/test/run-sa.sh Executable file
View file

@ -0,0 +1,13 @@
#!/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

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

View file

@ -0,0 +1,34 @@
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

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

View file

@ -0,0 +1,40 @@
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

160
addons/ot/test/sa/ot.cfg Normal file
View file

@ -0,0 +1,160 @@
[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

117
addons/ot/test/test-speed.sh Executable file
View file

@ -0,0 +1,117 @@
#!/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,7 +259,6 @@ listed below. Metrics from extra counters are not listed.
| haproxy_frontend_failed_header_rewriting_total | | haproxy_frontend_failed_header_rewriting_total |
| haproxy_frontend_http_cache_lookups_total | | haproxy_frontend_http_cache_lookups_total |
| haproxy_frontend_http_cache_hits_total | | haproxy_frontend_http_cache_hits_total |
| haproxy_frontend_http_cache_hint_hits_total |
| haproxy_frontend_internal_errors_total | | haproxy_frontend_internal_errors_total |
+-------------------------------------------------+ +-------------------------------------------------+
@ -330,7 +329,6 @@ listed below. Metrics from extra counters are not listed.
| haproxy_backend_connection_reuses_total | | haproxy_backend_connection_reuses_total |
| haproxy_backend_http_cache_lookups_total | | haproxy_backend_http_cache_lookups_total |
| haproxy_backend_http_cache_hits_total | | haproxy_backend_http_cache_hits_total |
| haproxy_backend_http_cache_hint_hits_total |
| haproxy_backend_max_queue_time_seconds | | haproxy_backend_max_queue_time_seconds |
| haproxy_backend_max_connect_time_seconds | | haproxy_backend_max_connect_time_seconds |
| haproxy_backend_max_response_time_seconds | | haproxy_backend_max_response_time_seconds |

View file

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

View file

@ -140,8 +140,6 @@ proposed deployment layout is the following:
| | +-- patches-3.0 # patches from development branch 3.0 | | +-- patches-3.0 # patches from development branch 3.0
| | | |
| +-- out # report directory (HTML) | +-- out # report directory (HTML)
| |
| +-- overlay # shared review state (git repo, optional)
| |
+- prog +- prog
| | | |
@ -257,91 +255,6 @@ proposed deployment layout is the following:
2 instances for 8 cores / 16 threads gives optimal results on dual memory 2 instances for 8 cores / 16 threads gives optimal results on dual memory
channel systems. 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 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 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 reasonably fast during the early development stages (before -dev1) but
@ -407,14 +320,9 @@ 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), 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). 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 The choice is preselected by the scripts above, and since these are radio
buttons, the user is free to change this selection. A simple reload done buttons, the user is free to change this selection. Reloading will lose the
by Ctrl-R will usually preserve user changes because only the document user's choices. When changing a selection, the line's background changes to
defines them (radio buttons, input boxes). But a full reload (Ctrl-Shift-R) match a similar color tone, allowing to visually spot preselected patches.
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 - 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 explanation for the choice of the preselection, and try to always end with
@ -429,52 +337,6 @@ 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 backport to, etc. Elements that look like issues numbers and commit IDs are
turned to links to ease navigation. 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 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 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 only on "uncertain" and "wait", it can make sense to hide "N" and "Y" and click

View file

@ -1,834 +0,0 @@
#!/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

@ -1,41 +0,0 @@
#!/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,30 +24,20 @@ quit() {
#### Main #### Main
USAGE="Usage: ${0##*/} [ -h ] [ -b 'bkp_list' ] [ -v version ] patch..." USAGE="Usage: ${0##*/} [ -h ] [ -b 'bkp_list' ] patch..."
MYSELF="$0" MYSELF="$0"
GITURL="http://git.haproxy.org/?p=haproxy.git;a=commitdiff;h=" GITURL="http://git.haproxy.org/?p=haproxy.git;a=commitdiff;h="
ISSUES="https://github.com/haproxy/haproxy/issues/" ISSUES="https://github.com/haproxy/haproxy/issues/"
BKP="" BKP=""
VERSION=""
while [ -n "$1" -a -z "${1##-*}" ]; do while [ -n "$1" -a -z "${1##-*}" ]; do
case "$1" in case "$1" in
-h|--help) quit "$USAGE" ;; -h|--help) quit "$USAGE" ;;
-b) BKP="$2"; shift 2 ;; -b) BKP="$2"; shift 2 ;;
-v) VERSION="$2"; shift 2 ;;
*) die "$USAGE" ;; *) die "$USAGE" ;;
esac esac
done 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=( "$@" ) PATCHES=( "$@" )
if [ ${#PATCHES[@]} = 0 ]; then if [ ${#PATCHES[@]} = 0 ]; then
@ -141,474 +131,14 @@ input.y[type="radio"]:checked {
border: 2px solid black; border: 2px solid black;
background-color: $BT_Y; background-color: $BT_Y;
} }
/* shared reviewers' notes, shown below the AI explanation */
div.notes {
font-style: italic;
margin-top: 2px;
}
</style> </style>
<script type="text/javascript"><!-- <script type="text/javascript"><!--
var cgi_url = "cgi-bin/update.cgi";
var nb_patches = 0; var nb_patches = 0;
var cid = []; var cid = [];
var bkp = []; 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 // first line to review
var review = 0; var review = 0;
@ -725,7 +255,6 @@ function updt(line,value) {
} }
updt_table(line); updt_table(line);
updt_output(); updt_output();
updt_save_btn();
} }
function show_only(b,n,u,w,y) { function show_only(b,n,u,w,y) {
@ -738,42 +267,12 @@ function show_only(b,n,u,w,y) {
updt(0,"r"); 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> </script>
</HEAD> </HEAD>
EOF EOF
echo "<BODY>" 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 "<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_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>" 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>"
@ -884,11 +383,8 @@ for patch in "${PATCHES[@]}"; do
resp=$(echo "$resp" | sed -e "s|\([0-9a-f]\{7,40\}\)|<a href='${GITURL}\1'>\1</a>|g") 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 "<TD nowrap align=center ${bkp[$cid]:+style='background-color:${BG_B}'}>$seq_num<BR/>"
echo -n "<input type='radio' name='review' id='rv_$seq_num' onclick='updt($seq_num,\"r\");' ${do_check:+checked} title='Start review here'/></TD>" echo -n "<input type='radio' name='review' 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 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>"
# 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><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 "<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) />" 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) />"
@ -896,18 +392,7 @@ 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,\"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 "<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>"
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 "</TR>"
echo echo
((seq_num++)) ((seq_num++))
@ -921,23 +406,12 @@ for patch in "${PATCHES[@]}"; do
fi fi
done done
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 "<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 "</TABLE>" 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 "<P/>"
echo "<H3>Output:</H3>" echo "<H3>Output:</H3>"
echo "<textarea cols=120 rows=10 id='output'></textarea>" echo "<textarea cols=120 rows=10 id='output'></textarea>"
echo "<P/>" echo "<P/>"
echo "<script type='text/javascript'>nb_patches=$seq_num; review=$review; init_review(); init_ref(); updt_table(0); updt_output();</script>" echo "<script type='text/javascript'>nb_patches=$seq_num; review=$review; updt_table(0); updt_output();</script>"
echo "</BODY></HTML>" echo "</BODY></HTML>"

View file

@ -16,14 +16,6 @@ if [ -z "$BRANCH" ]; then
exit 1 exit 1
fi 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" # 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) STABLE=$(cd "$HAPROXY_DIR" && git describe --tags "v${BRANCH}-dev0^" |cut -f1,2 -d.|cut -f2- -dv)
@ -72,4 +64,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 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 # generate the output, takes 3-5 seconds
"$SCRIPTS_DIR"/post-ai.sh ${VERSION:+-v "$VERSION"} -b "${BKP[*]}" ${PATCHES_DIR}/*.m7bv02.txt > ${VERDICT_DIR}/verdict-${BRANCH}-m7bv02.html "$SCRIPTS_DIR"/post-ai.sh -b "${BKP[*]}" ${PATCHES_DIR}/*.m7bv02.txt > ${VERDICT_DIR}/verdict-${BRANCH}-m7bv02.html

View file

@ -3,7 +3,7 @@
Configuration Manual Configuration Manual
---------------------- ----------------------
version 3.5 version 3.5
2026/07/08 2026/06/25
This document covers the configuration language as implemented in the version 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 and it may as well condition some worker process parameters (see examples
from section 2.4 "Conditional blocks"). from section 2.4 "Conditional blocks").
In standalone mode (without "-W" option) the process behaves like a worker, In standalone mode (without "-W" option nor the "master-worker" keyword) the
except for variables "HAPROXY_MASTER_CLI" and "HAPROXY_MWORKER" which are not process behaves like a worker, except for variables "HAPROXY_MASTER_CLI" and
defined. "HAPROXY_MWORKER" which are not defined.
Some variables are marked as not usable and not modifiable: Some variables are marked as not usable and not modifiable:
@ -1255,6 +1255,8 @@ Example:
# 2.3. Environment variables). Its presence enables an additional listener. # 2.3. Environment variables). Its presence enables an additional listener.
global global
master-worker
.if defined(HAPROXY_MWORKER) .if defined(HAPROXY_MWORKER)
listen mwcli_px listen mwcli_px
bind :1111 bind :1111
@ -1267,6 +1269,8 @@ Example:
# mworker-max-reloads parameter. # mworker-max-reloads parameter.
global global
master-worker
.if streq("$HAPROXY_BRANCH",3.1) .if streq("$HAPROXY_BRANCH",3.1)
mworker-max-reloads 5 mworker-max-reloads 5
.endif .endif
@ -1951,6 +1955,10 @@ The following keywords are supported in the "global" section :
- tune.quic.be.stream.rxbuf - tune.quic.be.stream.rxbuf
- tune.quic.be.tx.pacing - tune.quic.be.tx.pacing
- tune.quic.be.tx.udp-gso - 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.cubic-min-losses
- tune.quic.fe.cc.hystart - tune.quic.fe.cc.hystart
- tune.quic.fe.cc.max-frame-loss - tune.quic.fe.cc.max-frame-loss
@ -1966,8 +1974,18 @@ The following keywords are supported in the "global" section :
- tune.quic.fe.stream.rxbuf - tune.quic.fe.stream.rxbuf
- tune.quic.fe.tx.pacing - tune.quic.fe.tx.pacing
- tune.quic.fe.tx.udp-gso - 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.listen
- tune.quic.max-frame-loss (deprecated)
- tune.quic.mem.tx-max - 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.quic.zero-copy-fwd-send
- tune.renice.runtime - tune.renice.runtime
- tune.renice.startup - tune.renice.startup
@ -3032,6 +3050,32 @@ lua-prepend-path <string> [<type>]
See https://www.lua.org/pil/8.1.html for the details within the Lua See https://www.lua.org/pil/8.1.html for the details within the Lua
documentation. 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 master-worker no-exit-on-failure
In master-worker mode, by default, if a worker exits with a bad return code, 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 in the case of a segfault for example, all workers will be killed, and the
@ -3127,7 +3171,7 @@ pidfile <pidfile>
Writes PIDs of all daemons into file <pidfile> when daemon mode or writes PID 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 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 equivalent to the "-p" command line argument. The file must be accessible to
the user starting the process. See also "daemon". the user starting the process. See also "daemon" and "master-worker".
pp2-never-send-local pp2-never-send-local
A bug in the PROXY protocol v2 implementation was present in HAProxy up to A bug in the PROXY protocol v2 implementation was present in HAProxy up to
@ -5098,12 +5142,22 @@ tune.quic.fe.cc.cubic-min-losses <number>
compare some metrics. Never go beyond 2 without an expert's prior analysis of 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. 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.be.cc.hystart { on | off }
tune.quic.fe.cc.hystart { on | off } tune.quic.fe.cc.hystart { on | off }
Enables ('on') or disabled ('off') the HyStart++ (RFC 9406) algorithm for Enables ('on') or disabled ('off') the HyStart++ (RFC 9406) algorithm for
QUIC connections used as a replacement for the slow start phase of congestion 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. 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.be.cc.max-frame-loss <number>
tune.quic.fe.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 Sets the limit for which a single QUIC frame can be marked as lost. If
@ -5111,6 +5165,11 @@ tune.quic.fe.cc.max-frame-loss <number>
The default value is 10. 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.be.cc.max-win-size <size>
tune.quic.fe.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 Sets the default maximum window size for the congestion controller of a
@ -5126,6 +5185,11 @@ tune.quic.fe.cc.max-win-size <size>
See also the "quic-cc-algo" bind and server options. 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.be.cc.reorder-ratio <0..100, in percent>
tune.quic.fe.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 The ratio applied to the packet reordering threshold calculated. It may
@ -5133,6 +5197,11 @@ tune.quic.fe.cc.reorder-ratio <0..100, in percent>
The default value is 50. 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.be.max-idle-timeout <timeout>
tune.quic.fe.max-idle-timeout <timeout> tune.quic.fe.max-idle-timeout <timeout>
Sets the QUIC max_idle_timeout transport parameters on either frontend or Sets the QUIC max_idle_timeout transport parameters on either frontend or
@ -5146,6 +5215,11 @@ tune.quic.fe.max-idle-timeout <timeout>
The default value is 30s. 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.be.sec.glitches-threshold <number>
tune.quic.fe.sec.glitches-threshold <number> tune.quic.fe.sec.glitches-threshold <number>
Sets the threshold for the number of glitches per connection either on Sets the threshold for the number of glitches per connection either on
@ -5161,6 +5235,11 @@ tune.quic.fe.sec.glitches-threshold <number>
See also: fc_glitches, tune.glitches.kill.cpu-usage 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> tune.quic.fe.sec.retry-threshold <number>
Dynamically enables the Retry feature for all the configured QUIC listeners 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 as soon as this number of half open connections is reached. A half open
@ -5175,6 +5254,11 @@ tune.quic.fe.sec.retry-threshold <number>
See https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1.2 for more See https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1.2 for more
information about QUIC retry. 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 } tune.quic.fe.sock-per-conn { default-on | force-off }
Specifies globally how QUIC frontend connections will use socket for Specifies globally how QUIC frontend connections will use socket for
receive/send operations. Connections can share listener socket or each receive/send operations. Connections can share listener socket or each
@ -5204,6 +5288,12 @@ tune.quic.fe.sock-per-conn { default-on | force-off }
"force-off" is used globally, it will be applied on every listener instance, "force-off" is used globally, it will be applied on every listener instance,
regardless of their individual configuration. 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.be.stream.data-ratio <0..100, in percent>
tune.quic.fe.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 This setting allows to configure the hard limit of the number of data bytes
@ -5224,6 +5314,11 @@ tune.quic.fe.stream.data-ratio <0..100, in percent>
"tune.quic.be.stream.max-concurrent", "tune.quic.be.stream.max-concurrent",
"tune.quic.fe.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.be.stream.max-concurrent <number>
tune.quic.fe.stream.max-concurrent <number> tune.quic.fe.stream.max-concurrent <number>
On frontend side, this is used as the value for the advertised On frontend side, this is used as the value for the advertised
@ -5267,6 +5362,11 @@ tune.quic.fe.stream.max-total <number>
The default value is 0 which implies no specific limit outside of the QUIC The default value is 0 which implies no specific limit outside of the QUIC
protocol encoding limitation (2^60, more than a billion billion). 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.be.stream.rxbuf <size>
tune.quic.fe.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 This setting is the hard limit for the number of data bytes in flight over a
@ -5284,6 +5384,11 @@ tune.quic.fe.stream.rxbuf <size>
"tune.quic.fe.stream.max-concurrent", "tune.quic.fe.stream.max-concurrent",
"tune.quic.be.stream.data-ratio", "tune.quic.fe.stream.data-ratio" "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.be.tx.pacing { on | off }
tune.quic.fe.tx.pacing { on | off } tune.quic.fe.tx.pacing { on | off }
Enables ('on') or disables ('off') pacing support for QUIC emission. By Enables ('on') or disables ('off') pacing support for QUIC emission. By
@ -5295,6 +5400,11 @@ tune.quic.fe.tx.pacing { on | off }
See also the "quic-cc-algo" bind and server options. 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.be.tx.udp-gso { on | off }
tune.quic.fe.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 Enables ('on') or disables ('off') UDP GSO support for QUIC emission. By
@ -5303,6 +5413,11 @@ tune.quic.fe.tx.udp-gso { on | off }
be useful to disable it on developers suggestion when suspecting an issue on be useful to disable it on developers suggestion when suspecting an issue on
emission. 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 } tune.quic.listen { on | off }
Disable QUIC transport protocol on the frontend side. All the QUIC listeners 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, will still be created, but they won't listen for incoming datagrams. Hence,
@ -5322,6 +5437,11 @@ tune.quic.mem.tx-max <size>
connection will always be able to use a window of at least 2 datagrams, so a connection will always be able to use a window of at least 2 datagrams, so a
proper maxconn should be used in conjunction. 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 } tune.quic.zero-copy-fwd-send { on | off }
Enables ('on') of disabled ('off') the zero-copy sends of data for the QUIC Enables ('on') of disabled ('off') the zero-copy sends of data for the QUIC
multiplexer. It is enabled by default. multiplexer. It is enabled by default.
@ -6040,6 +6160,7 @@ default-server X - X X
default_backend X X X - default_backend X X X -
description - X X X description - X X X
disabled X X X X disabled X X X X
dispatch (deprecated) - - X X
email-alert from X X X X email-alert from X X X X
email-alert level X X X X email-alert level X X X X
email-alert mailers X X X X email-alert mailers X X X X
@ -6148,6 +6269,7 @@ option tcp-smart-accept (*) X X X -
option tcp-smart-connect (*) X - X X option tcp-smart-connect (*) X - X X
option tcpka X X X X option tcpka X X X X
option tcplog X X X - option tcplog X X X -
option transparent (deprecated) (*) X - X X
option use-small-buffers (*) X - X X option use-small-buffers (*) X - X X
persist rdp-cookie X - X X persist rdp-cookie X - X X
quic-initial X (!) X X - quic-initial X (!) X X -
@ -6209,6 +6331,7 @@ timeout server X - X X
timeout server-fin X - X X timeout server-fin X - X X
timeout tarpit X X X X timeout tarpit X X X X
timeout tunnel X - X X timeout tunnel X - X X
transparent (deprecated) X - X X
unique-id-format X X X X unique-id-format X X X X
unique-id-header X X X - unique-id-header X X X -
use_backend - X X - use_backend - X X -
@ -6623,7 +6746,7 @@ balance url_param <param> [check_post]
might be a URL parameter list. This is probably not a concern with SGML might be a URL parameter list. This is probably not a concern with SGML
type message bodies. type message bodies.
See also : "cookie", "hash-type". See also : "dispatch", "cookie", "transparent", "hash-type".
bind [<address>]:<port_range> [, ...] [param*] bind [<address>]:<port_range> [, ...] [param*]
@ -7404,6 +7527,54 @@ disabled
See also : "enabled", "force-be-switch" 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> dynamic-cookie-key <string>
Set the dynamic cookie secret key for a backend. Set the dynamic cookie secret key for a backend.
@ -11966,6 +12137,46 @@ option tcplog [clf]
See also : "option httplog", and section 8 about logging. 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 ]* option use-small-buffers [ queue | l7-retries | check ]*
Enable support for small buffers for the given categories. Enable support for small buffers for the given categories.
@ -12394,9 +12605,9 @@ server <name> <address>[:[port]] [param*]
address as the one from the client connection. This is useful in address as the one from the client connection. This is useful in
transparent proxy architectures where the client's connection is transparent proxy architectures where the client's connection is
intercepted and HAProxy must forward to the original destination intercepted and HAProxy must forward to the original destination
address. This is more or less what the old "transparent" keyword address. This is more or less what the "transparent" keyword does
did except that servers do not have the keyword's limitations except that with a server it's possible to limit concurrency and
(ssl, queues etc). Optionally, an address family prefix may be to report statistics. Optionally, an address family prefix may be
used before the address to force the family regardless of the used before the address to force the family regardless of the
address format, which can be useful to specify a path to a unix address format, which can be useful to specify a path to a unix
socket with no slash ('/'). Currently supported prefixes are : socket with no slash ('/'). Currently supported prefixes are :
@ -12758,9 +12969,7 @@ stats admin { if | unless } <cond>
matched. matched.
The admin level allows to enable/disable servers from the web interface. By The admin level allows to enable/disable servers from the web interface. By
default, statistics page is read-only for security reasons. If "stats scope" default, statistics page is read-only for security reasons.
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 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 buffer space, which means that if the list of servers is too long, the
@ -12795,8 +13004,8 @@ stats admin { if | unless } <cond>
stats http-request auth unless AUTH stats http-request auth unless AUTH
stats admin if AUTH_ADMIN stats admin if AUTH_ADMIN
See also : "stats enable", "stats auth", "stats http-request", "stats scope", See also : "stats enable", "stats auth", "stats http-request", section 12.2
section 12.2 about userlists and section 7 about ACL usage. about userlists and section 7 about ACL usage.
ssl-f-use [<sslbindconf> ...]* ssl-f-use [<sslbindconf> ...]*
Assignate a certificate to the current frontend. Assignate a certificate to the current frontend.
@ -13124,11 +13333,11 @@ stats scope { <name> | "." }
section in which the statement appears. section in which the statement appears.
When this statement is specified, only the sections enumerated with this When this statement is specified, only the sections enumerated with this
statement will appear in the report. All other ones will be hidden, and statement will appear in the report. All other ones will be hidden. This
attempts to change their state in admin mode will be rejected. This statement statement may appear as many times as needed if multiple sections need to be
may appear as many times as needed if multiple sections need to be reported. reported. Please note that the name checking is performed as simple string
Please note that the name checking is performed as simple string comparisons, comparisons, and that it is never checked that a give section name really
and that it is never checked that a give section name really exists. exists.
Though this statement alone is enough to enable statistics reporting, it is 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 recommended to set all other settings in order to avoid relying on default
@ -13152,8 +13361,7 @@ stats scope { <name> | "." }
stats uri /admin?stats stats uri /admin?stats
stats refresh 5s stats refresh 5s
See also : "stats auth", "stats enable", "stats realm", "stats uri" and See also : "stats auth", "stats enable", "stats realm", "stats uri"
"stats admin"
stats show-desc [ <desc> ] stats show-desc [ <desc> ]
@ -14954,6 +15162,32 @@ timeout tunnel <timeout>
See also : "timeout client", "timeout client-fin", "timeout server". 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> unique-id-format <fmt>
Generate a unique ID for each request. Generate a unique ID for each request.
@ -15418,7 +15652,7 @@ cache-store <name>
See section 6.2 about cache setup. See section 6.2 about cache setup.
cache-use <name> [no-early-hints] cache-use <name>
Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft
- | - | - | - | - | X | - | - - | - | - | - | - | X | - | -
@ -15427,12 +15661,6 @@ cache-use <name> [no-early-hints]
use a condition for both storage and delivering that's a good idea to put it use a condition for both storage and delivering that's a good idea to put it
after this one. 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. See section 6.2 about cache setup.
@ -16570,11 +16798,7 @@ set-retries <int> | <epxr>
Note that this action is only relevant on the backend side and thus this rule 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 is only available for the proxies with backend capability. It is also not
allowed in "defaults" sections. When the action is used for a listener, it is allowed in "defaults" sections.
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: Example:
tcp-request content set-retries 3 tcp-request content set-retries 3
@ -16662,11 +16886,7 @@ set-timeout { client | connect | queue | server | tarpit | tunnel } { <timeout>
the backend side and thus this rule is only available for the proxies with the backend side and thus this rule is only available for the proxies with
backend capabilities. Likewise, client timeout is only relevant for frontend backend capabilities. Likewise, client timeout is only relevant for frontend
side. Tarpit timeout is available to both sides. The timeout value must be side. Tarpit timeout is available to both sides. The timeout value must be
non-null to obtain the expected results. When the action is used for a non-null to obtain the expected results.
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: Example:
http-request set-timeout tunnel 5s http-request set-timeout tunnel 5s
@ -20249,33 +20469,6 @@ cache <name>
Declare a cache section, allocate a shared cache memory named <name>, the Declare a cache section, allocate a shared cache memory named <name>, the
size of cache is mandatory (see keyword "total-max-size" below). 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> max-age <seconds>
Define the maximum expiration duration. The expiration is set as the lowest 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 value between the s-maxage or max-age (in this order) directive in the
@ -24331,7 +24524,6 @@ bc_srv_queue integer
be_id integer be_id integer
be_name string be_name string
be_connect_timeout integer be_connect_timeout integer
be_max_retries integer
be_queue_timeout integer be_queue_timeout integer
be_server_timeout integer be_server_timeout integer
be_tarpit_timeout integer be_tarpit_timeout integer
@ -24340,7 +24532,6 @@ bytes_in integer
bytes_out integer bytes_out integer
cur_connect_timeout integer cur_connect_timeout integer
cur_client_timeout integer cur_client_timeout integer
cur_max_retries integer
cur_queue_timeout integer cur_queue_timeout integer
cur_server_timeout integer cur_server_timeout integer
cur_tarpit_timeout integer cur_tarpit_timeout integer
@ -24677,11 +24868,6 @@ be_connect_timeout : integer
current backend. This timeout can be overwritten by a "set-timeout" rule. See current backend. This timeout can be overwritten by a "set-timeout" rule. See
also the "cur_connect_timeout". 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 be_name : string
Returns a string containing the current backend's name. It can be used in 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 frontends with responses to check which backend processed the request. If
@ -24725,11 +24911,6 @@ cur_client_timeout : integer
In the default case, this will be equal to fe_client_timeout unless a 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". "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 cur_queue_timeout : integer
Returns the currently applied queue timeout in millisecond for the stream. 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 In the default case, this will be equal to be_queue_timeout unless a
@ -30512,9 +30693,41 @@ See also: "use-fcgi-app", section 9.2 about the compression filter, section 9.4
9.6. OpenTracing 9.6. OpenTracing
---------------- ----------------
The OpenTracing filter was removed in version 3.5. A replacement filter based The OpenTracing filter adds native support for using distributed tracing in
on OpenTelemetry is available since 3.4 with complete build instructions HAProxy. This is enabled by sending an OpenTracing compliant request to one
currently at: 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:
https://github.com/haproxytech/haproxy-opentelemetry/ https://github.com/haproxytech/haproxy-opentelemetry/

View file

@ -1,146 +0,0 @@
------
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: HATerm supports HTTPS/SSL/TCP:
$ make TARGET=linux-glibc USE_OPENSSL=1 haterm $ make TARGET=linux-glibc USE_OPENSSL=1
It also supports QUIC: It also supports QUIC:

View file

@ -1367,16 +1367,6 @@ Here is the list of static fields using the proxy statistics domain:
since the worker process started since the worker process started
112. proto [L...]: protocol 112. proto [L...]: protocol
113. priv_idle_cur [...S]: current number of private idle connections 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 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 not guaranteed. In this case, the header line should always be used to parse
@ -2158,7 +2148,8 @@ del backend <name>
There is additional restrictions which prevent backend removal. First, a There is additional restrictions which prevent backend removal. First, a
backend cannot be removed if it is explicitly referenced by config elements, 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 for example via a use_backend rule or in sample expressions. Some proxies
options are also incompatible with runtime deletion. Currently, a backend options are also incompatible with runtime deletion. Currently, this is the
case when deprecated dispatch or option transparent are used. Also, a backend
cannot be removed if there is a stick-table declared in it. Finally, it is 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. impossible for now to remove a backend if QUIC servers were present in it.
@ -2710,46 +2701,6 @@ set server <backend>/<server> ssl [ on | off ] (deprecated)
This command is deprecated, create a new server dynamically with or without This command is deprecated, create a new server dynamically with or without
SSL instead, using the "add server" command. 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 ] set severity-output [ none | number | string ]
Change the severity output format of the stats socket connected to for the Change the severity output format of the stats socket connected to for the
duration of the current session. duration of the current session.
@ -3157,7 +3108,7 @@ show events [<sink>] [-w] [-n] [-0]
delimited by a line feed character ('\n' or 10 or 0x0A). It is possible to 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. change this to the NUL character ('\0' or 0) by passing the "-0" argument.
show fd [-!plcfbs]* [[<tgid>]/[<fd>] | <fd>] show fd [-!plcfbsd]* [[<tgid>]/[<fd>] | <fd>]
Dump the list of either all open file descriptors or just the one number <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 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>/" be empty as a wildcard ("/<fd>" for fd <fd> across thread groups, "<tgid>/"
@ -3167,28 +3118,30 @@ show fd [-!plcfbs]* [[<tgid>]/[<fd>] | <fd>]
types. When '-' or '!' are encountered, the selection is inverted for the types. When '-' or '!' are encountered, the selection is inverted for the
following characters in the same argument. The inversion is reset before each following characters in the same argument. The inversion is reset before each
argument word delimited by white spaces. Selectable FD types include 'p' for argument word delimited by white spaces. Selectable FD types include 'p' for
pipes, 'l' for listeners, 'c' for connections (any type, shortcut for 'fb'), pipes, 'l' for listeners, 'c' for connections (any type), 'f' for frontend
'f' for frontend connections, 'b' or 's' for backend connections to servers. connections, 'b' for backend connections (any type), 's' for connections to
'c!f' is equivalent to 'b' ("any connections except frontend connections" are servers, 'd' for connections to the "dispatch" address or the backend's
indeed backend connections). This is only aimed at developers who need to transparent address. With this, 'b' is a shortcut for 'sd' and 'c' for 'fb' or
observe internal states in order to debug complex issues such as abnormal CPU 'fsd'. 'c!f' is equivalent to 'b' ("any connections except frontend
usages. One fd is reported per lines, and for each of them, its state in the connections" are indeed backend connections). This is only aimed at developers
poller using upper case letters for enabled flags and lower case for disabled who need to observe internal states in order to debug complex issues such as
flags, using "P" for "polled", "R" for "ready", "A" for "active", the events abnormal CPU usages. One fd is reported per lines, and for each of them, its
status using "H" for "hangup", "E" for "error", "O" for "output", "P" for state in the poller using upper case letters for enabled flags and lower case
"priority" and "I" for "input", a few other flags like "N" for "new" (just for disabled flags, using "P" for "polled", "R" for "ready", "A" for "active",
added into the fd cache), "U" for "updated" (received an update in the fd the events status using "H" for "hangup", "E" for "error", "O" for "output",
cache), "L" for "linger_risk", "C" for "cloned", then the cached entry "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 position, the pointer to the internal owner, the pointer to the I/O callback
its name when known. When the owner is a connection, the connection and and its name when known. When the owner is a connection, the connection flags,
flags, and the target are reported (frontend, proxy or server). When the and the target are reported (frontend, proxy or server). When the owner is a
owner is a listener, the listener's state and its frontend are reported. listener, the listener's state and its frontend are reported. There is no
There is no point in using this command without a good knowledge of the point in using this command without a good knowledge of the internals. It's
internals. It's worth noting that the output format may evolve over time so worth noting that the output format may evolve over time so this output must
this output must not be parsed by tools designed to be durable. Some internal not be parsed by tools designed to be durable. Some internal structure states
structure states may look suspicious to the function listing them, in this may look suspicious to the function listing them, in this case the output line
case the output line will be suffixed with an exclamation mark ('!'). This will be suffixed with an exclamation mark ('!'). This may help find a starting
may help find a starting point when trying to diagnose an incident. point when trying to diagnose an incident.
show info [typed|json] [desc] [float] show info [typed|json] [desc] [float]
Dump info about haproxy status on current process. If "typed" is passed as an Dump info about haproxy status on current process. If "typed" is passed as an

View file

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

View file

@ -51,7 +51,6 @@ struct buffer *get_trash_chunk(void);
struct buffer *get_large_trash_chunk(void); struct buffer *get_large_trash_chunk(void);
struct buffer *get_small_trash_chunk(void); struct buffer *get_small_trash_chunk(void);
struct buffer *get_trash_chunk_sz(size_t size); 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); struct buffer *get_larger_trash_chunk(struct buffer *chunk);
int init_trash_buffers(int first); int init_trash_buffers(int first);
@ -173,21 +172,6 @@ static forceinline struct buffer *alloc_trash_chunk_sz(size_t size)
else else
return NULL; 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. * free a trash chunk allocated by alloc_trash_chunk(). NOP on NULL.

View file

@ -591,15 +591,4 @@
# endif # endif
#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 */ #endif /* _HAPROXY_COMPILER_H */

View file

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