Compare commits

..

No commits in common. "master" and "v3.0-dev7" have entirely different histories.

1109 changed files with 48717 additions and 161044 deletions

View file

@ -1,15 +1,15 @@
FreeBSD_task:
freebsd_instance:
matrix:
image_family: freebsd-14-3
image_family: freebsd-13-2
only_if: $CIRRUS_BRANCH =~ 'master|next'
install_script:
- pkg update -f && pkg upgrade -y && pkg install -y openssl git gmake lua54 socat pcre2
- pkg update -f && pkg upgrade -y && pkg install -y openssl git gmake lua53 socat pcre
script:
- sudo sysctl kern.corefile=/tmp/%N.%P.core
- sudo sysctl kern.sugid_coredump=1
- scripts/build-vtest.sh
- gmake CC=clang V=1 ERR=1 TARGET=freebsd USE_ZLIB=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_OPENSSL=1 USE_LUA=1 LUA_INC=/usr/local/include/lua54 LUA_LIB=/usr/local/lib LUA_LIB_NAME=lua-5.4
- gmake CC=clang V=1 ERR=1 TARGET=freebsd USE_ZLIB=1 USE_PCRE=1 USE_OPENSSL=1 USE_LUA=1 LUA_INC=/usr/local/include/lua53 LUA_LIB=/usr/local/lib LUA_LIB_NAME=lua-5.3
- ./haproxy -vv
- ldd haproxy
test_script:

View file

@ -1,48 +0,0 @@
name: 'setup VTest'
description: 'ssss'
runs:
using: "composite"
steps:
- name: Setup coredumps
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
shell: sh
run: |
sudo sysctl -w fs.suid_dumpable=1
sudo sysctl kernel.core_pattern=/tmp/core.%h.%e.%t
- name: Setup ulimit for core dumps
shell: sh
run: |
# This is required for macOS which does not actually allow to increase
# the '-n' soft limit to the hard limit, thus failing to run.
ulimit -n 65536
ulimit -c unlimited
- name: Get VTest latest commit SHA
id: vtest-sha
shell: sh
run: |
echo "sha=$(git ls-remote https://code.vinyl-cache.org/vtest/VTest2 HEAD | cut -f1)" >> $GITHUB_OUTPUT
- name: Cache VTest
id: cache-vtest
uses: actions/cache@v5
with:
path: ${{ github.workspace }}/vtest
key: vtest-${{ runner.os }}-${{ runner.arch }}-${{ steps.vtest-sha.outputs.sha }}
- name: Install VTest
if: ${{ steps.cache-vtest.outputs.cache-hit != 'true' }}
shell: sh
run: |
DESTDIR=${{ github.workspace }}/vtest scripts/build-vtest.sh
- name: Install problem matcher for VTest
shell: sh
# This allows one to more easily see which tests fail.
run: echo "::add-matcher::.github/vtest.json"

View file

@ -19,9 +19,9 @@ defaults
frontend h2
mode http
bind 127.0.0.1:8443 ssl crt reg-tests/ssl/certs/common.pem alpn h2,http/1.1
default_backend h2b
bind 127.0.0.1:8443 ssl crt reg-tests/ssl/common.pem alpn h2,http/1.1
default_backend h2
backend h2b
backend h2
errorfile 200 .github/errorfile
http-request deny deny_status 200

203
.github/matrix.py vendored
View file

@ -12,7 +12,6 @@ import functools
import json
import re
import sys
import urllib.error
import urllib.request
from os import environ
from packaging import version
@ -20,10 +19,9 @@ from packaging import version
#
# this CI is used for both development and stable branches of HAProxy
#
# naming convention used, if branch/tag name matches:
# naming convention used, if branch name matches:
#
# "haproxy-" - stable branches
# "vX.Y.Z" - release tags
# otherwise - development branch (i.e. "latest" ssl variants, "latest" github images)
#
@ -34,24 +32,13 @@ def get_all_github_tags(url):
headers = {}
if environ.get("GITHUB_TOKEN") is not None:
headers["Authorization"] = "token {}".format(environ.get("GITHUB_TOKEN"))
all_tags = []
page = 1
sep = "&" if "?" in url else "?"
while True:
paginated_url = "{}{}per_page=100&page={}".format(url, sep, page)
request = urllib.request.Request(paginated_url, headers=headers)
try:
response = urllib.request.urlopen(request)
except urllib.error.URLError:
return all_tags if all_tags else None
tags = json.loads(response.read().decode("utf-8"))
if not tags:
break
all_tags.extend([tag['name'] for tag in tags])
if len(tags) < 100:
break
page += 1
return all_tags if all_tags else None
request = urllib.request.Request(url, headers=headers)
try:
tags = urllib.request.urlopen(request)
except:
return None
tags = json.loads(tags.read().decode("utf-8"))
return [tag['name'] for tag in tags]
@functools.lru_cache(5)
def determine_latest_openssl(ssl):
@ -69,7 +56,7 @@ def aws_lc_version_string_to_num(version_string):
return tuple(map(int, version_string[1:].split('.')))
def aws_lc_version_valid(version_string):
return re.match(r'^v[0-9]+(\.[0-9]+)*$', version_string)
return re.match('^v[0-9]+(\.[0-9]+)*$', version_string)
@functools.lru_cache(5)
def determine_latest_aws_lc(ssl):
@ -77,43 +64,9 @@ def determine_latest_aws_lc(ssl):
if not tags:
return "AWS_LC_VERSION=failed_to_detect"
valid_tags = list(filter(aws_lc_version_valid, tags))
if not valid_tags:
return "AWS_LC_VERSION=failed_to_detect"
latest_tag = max(valid_tags, key=aws_lc_version_string_to_num)
return "AWS_LC_VERSION={}".format(latest_tag[1:])
def aws_lc_fips_version_string_to_num(version_string):
return tuple(map(int, version_string[12:].split('.')))
def aws_lc_fips_version_valid(version_string):
return re.match(r'^AWS-LC-FIPS-[0-9]+(\.[0-9]+)*$', version_string)
@functools.lru_cache(5)
def determine_latest_aws_lc_fips(ssl):
tags = get_all_github_tags("https://api.github.com/repos/aws/aws-lc/tags")
if not tags:
return "AWS_LC_FIPS_VERSION=failed_to_detect"
valid_tags = list(filter(aws_lc_fips_version_valid, tags))
if not valid_tags:
return "AWS_LC_FIPS_VERSION=failed_to_detect"
latest_tag = max(valid_tags, key=aws_lc_fips_version_string_to_num)
return "AWS_LC_FIPS_VERSION={}".format(latest_tag[12:])
def wolfssl_version_string_to_num(version_string):
return tuple(map(int, version_string[1:].removesuffix('-stable').split('.')))
def wolfssl_version_valid(version_string):
return re.match(r'^v[0-9]+(\.[0-9]+)*-stable$', version_string)
@functools.lru_cache(5)
def determine_latest_wolfssl(ssl):
tags = get_all_github_tags("https://api.github.com/repos/wolfssl/wolfssl/tags")
if not tags:
return "WOLFSSL_VERSION=failed_to_detect"
valid_tags = list(filter(wolfssl_version_valid, tags))
latest_tag = max(valid_tags, key=wolfssl_version_string_to_num)
return "WOLFSSL_VERSION={}".format(latest_tag[1:].removesuffix('-stable'))
@functools.lru_cache(5)
def determine_latest_libressl(ssl):
try:
@ -136,18 +89,14 @@ def clean_compression(compression):
def main(ref_name):
print("Generating matrix for branch '{}'.".format(ref_name))
is_stable = "haproxy-" in ref_name or re.match(r'^v\d+\.\d+\.\d+$', ref_name)
matrix = []
# Ubuntu
if is_stable:
os = "ubuntu-24.04" # stable branch
os_arm = "ubuntu-24.04-arm" # stable branch
if "haproxy-" in ref_name:
os = "ubuntu-22.04" # stable branch
else:
os = "ubuntu-24.04" # development branch
os_arm = "ubuntu-24.04-arm" # development branch
os = "ubuntu-latest" # development branch
TARGET = "linux-glibc"
for CC in ["gcc", "clang"]:
@ -168,16 +117,17 @@ def main(ref_name):
"TARGET": TARGET,
"CC": CC,
"FLAGS": [
'DEBUG="-DDEBUG_LIST"',
'DEBUG_CFLAGS="-DDEBUG_LIST"',
"USE_ZLIB=1",
"USE_OT=1",
"OT_INC=${HOME}/opt-ot/include",
"OT_LIB=${HOME}/opt-ot/lib",
"OT_RUNPATH=1",
"USE_PCRE2=1",
"USE_PCRE2_JIT=1",
"USE_PCRE=1",
"USE_PCRE_JIT=1",
"USE_LUA=1",
"USE_OPENSSL=1",
"USE_SYSTEMD=1",
"USE_WURFL=1",
"WURFL_INC=addons/wurfl/dummy",
"WURFL_LIB=addons/wurfl/dummy",
@ -192,37 +142,38 @@ def main(ref_name):
# ASAN
for os_asan in [os, os_arm]:
matrix.append(
{
"name": "{}, {}, ASAN, all features".format(os_asan, CC),
"os": os_asan,
"TARGET": TARGET,
"CC": CC,
"FLAGS": [
"USE_OBSOLETE_LINKER=1",
'ARCH_FLAGS="-g -fsanitize=address"',
'OPT_CFLAGS="-O1"',
"USE_ZLIB=1",
"USE_OT=1",
"OT_INC=${HOME}/opt-ot/include",
"OT_LIB=${HOME}/opt-ot/lib",
"OT_RUNPATH=1",
"USE_PCRE2=1",
"USE_PCRE2_JIT=1",
"USE_LUA=1",
"USE_OPENSSL=1",
"USE_WURFL=1",
"WURFL_INC=addons/wurfl/dummy",
"WURFL_LIB=addons/wurfl/dummy",
"USE_DEVICEATLAS=1",
"DEVICEATLAS_SRC=addons/deviceatlas/dummy",
"USE_PROMEX=1",
"USE_51DEGREES=1",
"51DEGREES_SRC=addons/51degrees/dummy/pattern",
],
}
)
matrix.append(
{
"name": "{}, {}, ASAN, all features".format(os, CC),
"os": os,
"TARGET": TARGET,
"CC": CC,
"FLAGS": [
"USE_OBSOLETE_LINKER=1",
'DEBUG_CFLAGS="-g -fsanitize=address"',
'LDFLAGS="-fsanitize=address"',
'CPU_CFLAGS.generic="-O1"',
"USE_ZLIB=1",
"USE_OT=1",
"OT_INC=${HOME}/opt-ot/include",
"OT_LIB=${HOME}/opt-ot/lib",
"OT_RUNPATH=1",
"USE_PCRE=1",
"USE_PCRE_JIT=1",
"USE_LUA=1",
"USE_OPENSSL=1",
"USE_SYSTEMD=1",
"USE_WURFL=1",
"WURFL_INC=addons/wurfl/dummy",
"WURFL_LIB=addons/wurfl/dummy",
"USE_DEVICEATLAS=1",
"DEVICEATLAS_SRC=addons/deviceatlas/dummy",
"USE_PROMEX=1",
"USE_51DEGREES=1",
"51DEGREES_SRC=addons/51degrees/dummy/pattern",
],
}
)
for compression in ["USE_ZLIB=1"]:
matrix.append(
@ -239,14 +190,13 @@ def main(ref_name):
"stock",
"OPENSSL_VERSION=1.0.2u",
"OPENSSL_VERSION=1.1.1s",
"OPENSSL_VERSION=3.5.1",
"QUICTLS_VERSION=OpenSSL_1_1_1w-quic1",
"WOLFSSL_VERSION=5.7.0",
"AWS_LC_VERSION=1.39.0",
"QUICTLS=yes",
"WOLFSSL_VERSION=5.6.6",
"AWS_LC_VERSION=1.16.0",
# "BORINGSSL=yes",
]
if not is_stable: # development branch
if "haproxy-" not in ref_name: # development branch
ssl_versions = ssl_versions + [
"OPENSSL_VERSION=latest",
"LIBRESSL_VERSION=latest",
@ -254,7 +204,8 @@ def main(ref_name):
for ssl in ssl_versions:
flags = ["USE_OPENSSL=1"]
skipdup=0
if ssl == "BORINGSSL=yes" or ssl == "QUICTLS=yes" or "LIBRESSL" in ssl or "WOLFSSL" in ssl or "AWS_LC" in ssl:
flags.append("USE_QUIC=1")
if "WOLFSSL" in ssl:
flags.append("USE_OPENSSL_WOLFSSL=1")
if "AWS_LC" in ssl:
@ -264,23 +215,8 @@ def main(ref_name):
flags.append("SSL_INC=${HOME}/opt/include")
if "LIBRESSL" in ssl and "latest" in ssl:
ssl = determine_latest_libressl(ssl)
skipdup=1
if "OPENSSL" in ssl and "latest" in ssl:
ssl = determine_latest_openssl(ssl)
skipdup=1
# if "latest" equals a version already in the list
if ssl in ssl_versions and skipdup == 1:
continue
openssl_supports_quic = False
try:
openssl_supports_quic = version.Version(ssl.split("OPENSSL_VERSION=",1)[1]) >= version.Version("3.5.0")
except:
pass
if ssl == "BORINGSSL=yes" or "QUICTLS" in ssl or "LIBRESSL" in ssl or "WOLFSSL" in ssl or "AWS_LC" in ssl or openssl_supports_quic:
flags.append("USE_QUIC=1")
matrix.append(
{
@ -293,21 +229,24 @@ def main(ref_name):
}
)
# macOS on dev branches
if not is_stable:
os = "macos-26" # development branch
# macOS
TARGET = "osx"
for CC in ["clang"]:
matrix.append(
{
"name": "{}, {}, no features".format(os, CC),
"os": os,
"TARGET": TARGET,
"CC": CC,
"FLAGS": [],
}
)
if "haproxy-" in ref_name:
os = "macos-12" # stable branch
else:
os = "macos-latest" # development branch
TARGET = "osx"
for CC in ["clang"]:
matrix.append(
{
"name": "{}, {}, no features".format(os, CC),
"os": os,
"TARGET": TARGET,
"CC": CC,
"FLAGS": [],
}
)
# Print matrix

View file

@ -9,44 +9,34 @@ permissions:
contents: read
jobs:
Test:
name: ${{ matrix.name }}
test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: AWS-LC
command: "from matrix import determine_latest_aws_lc; print(determine_latest_aws_lc(''))"
- name: AWS-LC (FIPS)
command: "from matrix import determine_latest_aws_lc_fips; print(determine_latest_aws_lc_fips(''))"
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install VTest
run: |
scripts/build-vtest.sh
- name: Determine latest AWS-LC release
id: get_aws_lc_release
run: |
result=$(cd .github && python3 -c "${{ matrix.command }}")
result=$(cd .github && python3 -c "from matrix import determine_latest_aws_lc; print(determine_latest_aws_lc(''))")
echo $result
echo "result=$result" >> $GITHUB_OUTPUT
- name: Cache AWS-LC
id: cache_aws_lc
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: '~/opt/'
key: ssl-${{ steps.get_aws_lc_release.outputs.result }}-Ubuntu-latest-gcc
- name: Install apt dependencies
run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get --no-install-recommends -y install socat gdb jose
- name: Install AWS-LC
if: ${{ steps.cache_ssl.outputs.cache-hit != 'true' }}
run: env ${{ steps.get_aws_lc_release.outputs.result }} scripts/build-ssl.sh
- name: Compile HAProxy
run: |
make -j$(nproc) ERR=1 CC=gcc TARGET=linux-glibc \
make -j$(nproc) CC=gcc TARGET=linux-glibc \
USE_OPENSSL_AWSLC=1 USE_QUIC=1 \
SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
DEBUG="-DDEBUG_STRICT -DDEBUG_MEMORY_POOLS -DDEBUG_POOL_INTEGRITY" \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/"
sudo make install
- name: Show HAProxy version
@ -54,46 +44,23 @@ jobs:
run: |
ldd $(which haproxy)
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-vtest
echo "version=$(haproxy -v |awk 'NR==1{print $3}')" >> $GITHUB_OUTPUT
- name: Install problem matcher for VTest
run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest for HAProxy
id: vtest
run: |
make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Run Unit tests
id: unittests
run: |
make unit-tests
# This is required for macOS which does not actually allow to increase
# the '-n' soft limit to the hard limit, thus failing to run.
ulimit -n 65536
make reg-tests VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
for folder in ${TMPDIR:-/tmp}/haregtests-*/vtc.*; do
for folder in ${TMPDIR}/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done
exit 1
- name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
failed=false
shopt -s nullglob
for file in /tmp/core.*; do
failed=true
printf "::group::"
gdb -ex 'thread apply all bt full' ./haproxy $file
echo "::endgroup::"
done
if [ "$failed" = true ]; then
exit 1;
fi
- name: Show Unit-Tests results
if: ${{ failure() && steps.unittests.outcome == 'failure' }}
run: |
for result in ${TMPDIR:-/tmp}/ha-unittests-*/results/res.*; do
printf "::group::"
cat $result
echo "::endgroup::"
done
exit 1

View file

@ -3,7 +3,6 @@ name: Spelling Check
on:
schedule:
- cron: "0 0 * * 2"
workflow_dispatch:
permissions:
contents: read
@ -11,12 +10,12 @@ permissions:
jobs:
codespell:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
if: ${{ github.repository_owner == 'haproxy' }}
steps:
- uses: actions/checkout@v6
- uses: codespell-project/codespell-problem-matcher@v1.2.0
- uses: actions/checkout@v4
- uses: codespell-project/codespell-problem-matcher@v1
- uses: codespell-project/actions-codespell@master
with:
skip: CHANGELOG,Makefile,*.fig,*.pem,./doc/design-thoughts,./doc/internals
ignore_words_list: pres,ist,ists,hist,wan,ca,cas,que,ans,te,nd,referer,ot,uint,iif,fo,keep-alives,dosen,ifset,thrid,strack,ba,chck,hel,unx,mor,clen,collet,bu,htmp,siz,experim
ignore_words_list: ist,ists,hist,wan,ca,cas,que,ans,te,nd,referer,ot,uint,iif,fo,keep-alives,dosen,ifset,thrid,strack,ba,chck,hel,unx,mor,clen,collet,bu,htmp,siz,experim
uri_ignore_words_list: trafic,ressources

View file

@ -11,10 +11,15 @@ permissions:
jobs:
h2spec:
name: h2spec
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- TARGET: linux-glibc
CC: gcc
os: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install h2spec
id: install-h2spec
run: |
@ -23,13 +28,13 @@ jobs:
tar xvf h2spec.tar.gz
sudo install -m755 h2spec /usr/local/bin/h2spec
echo "version=${H2SPEC_VERSION}" >> $GITHUB_OUTPUT
- name: Compile HAProxy with gcc
- name: Compile HAProxy with ${{ matrix.CC }}
run: |
make -j$(nproc) all \
ERR=1 \
TARGET=linux-glibc \
CC=gcc \
DEBUG="-DDEBUG_POOL_INTEGRITY" \
TARGET=${{ matrix.TARGET }} \
CC=${{ matrix.CC }} \
DEBUG="-DDEBUG_STRICT -DDEBUG_MEMORY_POOLS -DDEBUG_POOL_INTEGRITY" \
USE_OPENSSL=1
sudo make install
- name: Show HAProxy version
@ -45,7 +50,7 @@ jobs:
fi
echo "::endgroup::"
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
echo "version=$(haproxy -v |awk 'NR==1{print $3}')" >> $GITHUB_OUTPUT
- name: Launch HAProxy ${{ steps.show-version.outputs.version }}
run: haproxy -f .github/h2spec.config -D
- name: Run h2spec ${{ steps.install-h2spec.outputs.version }}

View file

@ -7,27 +7,19 @@ permissions:
contents: read
jobs:
compile:
name: ${{ matrix.name }}
runs-on: ubuntu-slim
strategy:
matrix:
include:
- name: dev/flags/
targets:
- dev/flags/flags
- name: dev/haring/
targets:
- dev/haring/haring
- name: dev/hpack/
targets:
- dev/hpack/decode
- dev/hpack/gen-enc
- dev/hpack/gen-rht
- name: dev/poll/
targets:
- dev/poll/poll
fail-fast: false
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: make ${{ join(matrix.targets, ' ') }}
- uses: actions/checkout@v4
- name: Compile admin/halog/halog
run: |
make admin/halog/halog
- name: Compile dev/flags/flags
run: |
make dev/flags/flags
- name: Compile dev/poll/poll
run: |
make dev/poll/poll
- name: Compile dev/hpack
run: |
make dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht

View file

@ -15,19 +15,18 @@ permissions:
jobs:
scan:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
if: ${{ github.repository_owner == 'haproxy' }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install apt dependencies
run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get --no-install-recommends -y install \
liblua5.4-dev \
libpcre2-dev \
sudo apt-get update
sudo apt-get install -y \
liblua5.3-dev \
libsystemd-dev
- name: Install QUICTLS
run: |
QUICTLS_VERSION=OpenSSL_1_1_1w-quic1 scripts/build-ssl.sh
QUICTLS=yes scripts/build-ssl.sh
- name: Download Coverity build tool
run: |
wget -c -N https://scan.coverity.com/download/linux64 --post-data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=Haproxy" -O coverity_tool.tar.gz
@ -38,7 +37,7 @@ jobs:
- name: Build with Coverity build tool
run: |
export PATH=`pwd`/coverity_tool/bin:$PATH
cov-build --dir cov-int make CC=clang TARGET=linux-glibc USE_ZLIB=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_LUA=1 USE_OPENSSL=1 USE_QUIC=1 USE_WURFL=1 WURFL_INC=addons/wurfl/dummy WURFL_LIB=addons/wurfl/dummy USE_DEVICEATLAS=1 DEVICEATLAS_SRC=addons/deviceatlas/dummy USE_51DEGREES=1 51DEGREES_SRC=addons/51degrees/dummy/pattern ADDLIB=\"-Wl,-rpath,$HOME/opt/lib/\" SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include DEBUG+=-DDEBUG_STRICT=2 DEBUG+=-DDEBUG_USE_ABORT=1
cov-build --dir cov-int make CC=clang TARGET=linux-glibc USE_ZLIB=1 USE_PCRE=1 USE_PCRE_JIT=1 USE_LUA=1 USE_OPENSSL=1 USE_QUIC=1 USE_SYSTEMD=1 USE_WURFL=1 WURFL_INC=addons/wurfl/dummy WURFL_LIB=addons/wurfl/dummy USE_DEVICEATLAS=1 DEVICEATLAS_SRC=addons/deviceatlas/dummy USE_51DEGREES=1 51DEGREES_SRC=addons/51degrees/dummy/pattern ADDLIB=\"-Wl,-rpath,$HOME/opt/lib/\" SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include DEBUG+=-DDEBUG_STRICT=1 DEBUG+=-DDEBUG_USE_ABORT=1
- name: Submit build result to Coverity Scan
run: |
tar czvf cov.tar.gz cov-int

View file

@ -6,7 +6,6 @@ name: Cross Compile
on:
schedule:
- cron: "0 0 21 * *"
workflow_dispatch:
permissions:
contents: read
@ -91,20 +90,20 @@ jobs:
}
]
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
if: ${{ github.repository_owner == 'haproxy' }}
steps:
- name: install packages
run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get update
sudo apt-get -yq --force-yes install \
gcc-${{ matrix.platform.arch }} \
${{ matrix.platform.libs }}
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: install quictls
run: |
QUICTLS_EXTRA_ARGS="--cross-compile-prefix=${{ matrix.platform.arch }}- ${{ matrix.platform.target }}" QUICTLS_VERSION=OpenSSL_1_1_1w-quic1 scripts/build-ssl.sh
QUICTLS_EXTRA_ARGS="--cross-compile-prefix=${{ matrix.platform.arch }}- ${{ matrix.platform.target }}" QUICTLS=yes scripts/build-ssl.sh
- name: Build
run: |

View file

@ -1,9 +1,8 @@
name: Fedora/Rawhide/OpenSSL
name: Fedora/Rawhide/QuicTLS
on:
schedule:
- cron: "0 0 25 * *"
workflow_dispatch:
permissions:
contents: read
@ -13,24 +12,26 @@ jobs:
strategy:
matrix:
platform: [
{ name: x64, cc: gcc, ADDLIB_ATOMIC: "", ARCH_FLAGS: "" },
{ name: x64, cc: clang, ADDLIB_ATOMIC: "", ARCH_FLAGS: "" },
{ name: x86, cc: gcc, ADDLIB_ATOMIC: "-latomic", ARCH_FLAGS: "-m32" },
{ name: x86, cc: clang, ADDLIB_ATOMIC: "-latomic", ARCH_FLAGS: "-m32" }
{ name: x64, cc: gcc, QUICTLS_EXTRA_ARGS: "", ADDLIB_ATOMIC: "", DEBUG_CFLAGS: "", LDFLAGS: "" },
{ name: x64, cc: clang, QUICTLS_EXTRA_ARGS: "", ADDLIB_ATOMIC: "", DEBUG_CFLAGS: "", LDFLAGS: "" },
{ name: x86, cc: gcc, QUICTLS_EXTRA_ARGS: "-m32 linux-generic32", ADDLIB_ATOMIC: "-latomic", DEBUG_CFLAGS: "-m32", LDFLAGS: "-m32" },
{ name: x86, cc: clang, QUICTLS_EXTRA_ARGS: "-m32 linux-generic32", ADDLIB_ATOMIC: "-latomic", DEBUG_CFLAGS: "-m32", LDFLAGS: "-m32" }
]
fail-fast: false
name: ${{ matrix.platform.cc }}.${{ matrix.platform.name }}
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
if: ${{ github.repository_owner == 'haproxy' }}
container:
image: fedora:rawhide
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install dependencies
run: |
dnf -y install awk diffutils git pcre-devel zlib-devel pcre2-devel 'perl(FindBin)' perl-IPC-Cmd 'perl(File::Copy)' 'perl(File::Compare)' lua-devel socat findutils systemd-devel clang openssl-devel.x86_64
dnf -y install 'perl(FindBin)' 'perl(File::Compare)' perl-IPC-Cmd 'perl(File::Copy)' glibc-devel.i686 lua-devel.i686 lua-devel.x86_64 systemd-devel.i686 zlib-ng-compat-devel.i686 pcre-devel.i686 libatomic.i686 openssl-devel.i686
- uses: ./.github/actions/setup-vtest
dnf -y install diffutils git pcre-devel zlib-devel pcre2-devel 'perl(FindBin)' perl-IPC-Cmd 'perl(File::Copy)' 'perl(File::Compare)' lua-devel socat findutils systemd-devel clang
dnf -y install 'perl(FindBin)' 'perl(File::Compare)' perl-IPC-Cmd 'perl(File::Copy)' glibc-devel.i686 lua-devel.i686 lua-devel.x86_64 systemd-devel.i686 zlib-ng-compat-devel.i686 pcre-devel.i686 libatomic.i686
- name: Install VTest
run: scripts/build-vtest.sh
- name: Install QuicTLS
run: QUICTLS=yes QUICTLS_EXTRA_ARGS="${{ matrix.platform.QUICTLS_EXTRA_ARGS }}" scripts/build-ssl.sh
- name: Build contrib tools
run: |
make admin/halog/halog
@ -39,7 +40,7 @@ jobs:
make dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht
- name: Compile HAProxy with ${{ matrix.platform.cc }}
run: |
make -j3 CC=${{ matrix.platform.cc }} V=1 ERR=1 TARGET=linux-glibc DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" USE_PROMEX=1 USE_OPENSSL=1 USE_QUIC=1 USE_ZLIB=1 USE_PCRE=1 USE_PCRE_JIT=1 USE_LUA=1 ADDLIB="${{ matrix.platform.ADDLIB_ATOMIC }}" ARCH_FLAGS="${{ matrix.platform.ARCH_FLAGS }}"
make -j3 CC=${{ matrix.platform.cc }} V=1 ERR=1 TARGET=linux-glibc USE_OPENSSL=1 USE_QUIC=1 USE_ZLIB=1 USE_PCRE=1 USE_PCRE_JIT=1 USE_LUA=1 USE_SYSTEMD=1 ADDLIB="${{ matrix.platform.ADDLIB_ATOMIC }} -Wl,-rpath,${HOME}/opt/lib" SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include DEBUG_CFLAGS="${{ matrix.platform.DEBUG_CFLAGS }}" LDFLAGS="${{ matrix.platform.LDFLAGS }}"
make install
- name: Show HAProxy version
id: show-version
@ -48,28 +49,17 @@ jobs:
ldd $(command -v haproxy)
echo "::endgroup::"
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
#
# TODO: review this workaround later
- name: relax crypto policies
run: |
dnf -y install crypto-policies-scripts
echo LEGACY > /etc/crypto-policies/config
update-crypto-policies
echo "version=$(haproxy -v |awk 'NR==1{print $3}')" >> $GITHUB_OUTPUT
- name: Run VTest for HAProxy ${{ steps.show-version.outputs.version }}
id: vtest
run: |
make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel
make reg-tests VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
for folder in ${TMPDIR:-/tmp}/haregtests-*/vtc.*; do
for folder in ${TMPDIR}/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done
- name: Run Unit tests
id: unittests
run: |
make unit-tests

View file

@ -1,25 +0,0 @@
name: Illumos
on:
schedule:
- cron: "0 0 25 * *"
workflow_dispatch:
permissions:
contents: read
jobs:
gcc:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- name: "Checkout repository"
uses: actions/checkout@v6
- name: "Build on VM"
uses: vmactions/solaris-vm@v1
with:
prepare: |
pkg install gcc make
run: |
gmake CC=gcc TARGET=solaris USE_OPENSSL=1 USE_PROMEX=1

62
.github/workflows/musl.yml vendored Normal file
View file

@ -0,0 +1,62 @@
name: alpine/musl
on:
push:
permissions:
contents: read
jobs:
musl:
name: gcc
runs-on: ubuntu-latest
container:
image: alpine:latest
options: --privileged --ulimit core=-1 --security-opt seccomp=unconfined
volumes:
- /tmp/core:/tmp/core
steps:
- name: Setup coredumps
run: |
ulimit -c unlimited
echo '/tmp/core/core.%h.%e.%t' > /proc/sys/kernel/core_pattern
- uses: actions/checkout@v4
- name: Install dependencies
run: apk add gcc gdb make tar git python3 libc-dev linux-headers pcre-dev pcre2-dev openssl-dev lua5.3-dev grep socat curl musl-dbg lua5.3-dbg
- name: Install VTest
run: scripts/build-vtest.sh
- name: Build
run: make -j$(nproc) TARGET=linux-musl DEBUG_CFLAGS='-ggdb3' CC=cc V=1 USE_LUA=1 LUA_INC=/usr/include/lua5.3 LUA_LIB=/usr/lib/lua5.3 USE_OPENSSL=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_PROMEX=1
- name: Show version
run: ./haproxy -vv
- name: Show linked libraries
run: ldd haproxy
- name: Install problem matcher for VTest
# This allows one to more easily see which tests fail.
run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest
id: vtest
run: make reg-tests VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
failed=false
ls /tmp/core/
for file in /tmp/core/core.*; do
failed=true
printf "::group::"
gdb -ex 'thread apply all bt full' ./haproxy $file
echo "::endgroup::"
done
if [ "$failed" = true ]; then
exit 1;
fi
- name: Show results
if: ${{ failure() }}
run: |
for folder in /tmp/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done

View file

@ -3,23 +3,20 @@ name: NetBSD
on:
schedule:
- cron: "0 0 25 * *"
workflow_dispatch:
permissions:
contents: read
jobs:
gcc:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
permissions:
contents: read
steps:
- name: "Checkout repository"
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: "Build on VM"
uses: vmactions/netbsd-vm@v1
with:
prepare: |
/usr/sbin/pkg_add gmake curl
/usr/sbin/pkg_add gmake pcre2
run: |
gmake CC=gcc TARGET=netbsd ERR=1 USE_OPENSSL=1 USE_LUA=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_PROMEX=1 USE_ZLIB=1
gmake CC=gcc TARGET=netbsd USE_OPENSSL=1 USE_LUA=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_PROMEX=1 USE_ZLIB=1

View file

@ -1,80 +0,0 @@
name: openssl ECH
on:
schedule:
- cron: "0 3 * * *"
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v6
- name: Install apt dependencies
run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get --no-install-recommends -y install socat gdb
sudo apt-get --no-install-recommends -y install libpsl-dev
- uses: ./.github/actions/setup-vtest
- name: Install OpenSSL+ECH
run: env OPENSSL_VERSION="git-feature/ech" GIT_TYPE="branch" scripts/build-ssl.sh
- name: Install curl+ECH
run: env SSL_LIB=${HOME}/opt/ scripts/build-curl.sh
- name: Compile HAProxy
run: |
make -j$(nproc) CC=gcc TARGET=linux-glibc \
USE_QUIC=1 USE_OPENSSL=1 USE_ECH=1 \
SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/" \
ARCH_FLAGS="-ggdb3 -fsanitize=address"
sudo make install
- name: Show HAProxy version
id: show-version
run: |
ldd $(which haproxy)
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
- name: Install problem matcher for VTest
run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest for HAProxy
id: vtest
run: |
# This is required for macOS which does not actually allow to increase
# the '-n' soft limit to the hard limit, thus failing to run.
ulimit -n 65536
# allow to catch coredumps
ulimit -c unlimited
make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
for folder in ${TMPDIR:-/tmp}/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done
exit 1
- name: Run Unit tests
id: unittests
run: |
make unit-tests
- name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
failed=false
shopt -s nullglob
for file in /tmp/core.*; do
failed=true
printf "::group::"
gdb -ex 'thread apply all bt full' ./haproxy $file
echo "::endgroup::"
done
if [ "$failed" = true ]; then
exit 1;
fi

View file

@ -1,77 +0,0 @@
name: openssl master
on:
schedule:
- cron: "0 3 * * *"
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v6
- name: Install apt dependencies
run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get --no-install-recommends -y install socat gdb
sudo apt-get --no-install-recommends -y install libpsl-dev
- uses: ./.github/actions/setup-vtest
- name: Install OpenSSL master
run: env OPENSSL_VERSION="git-master" GIT_TYPE="branch" scripts/build-ssl.sh
- name: Compile HAProxy
run: |
make -j$(nproc) ERR=1 CC=gcc TARGET=linux-glibc \
USE_QUIC=1 USE_OPENSSL=1 \
SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/"
sudo make install
- name: Show HAProxy version
id: show-version
run: |
ldd $(which haproxy)
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
- name: Install problem matcher for VTest
run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest for HAProxy
id: vtest
run: |
# This is required for macOS which does not actually allow to increase
# the '-n' soft limit to the hard limit, thus failing to run.
ulimit -n 65536
# allow to catch coredumps
ulimit -c unlimited
make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
for folder in ${TMPDIR:-/tmp}/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done
exit 1
- name: Run Unit tests
id: unittests
run: |
make unit-tests
- name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
failed=false
shopt -s nullglob
for file in /tmp/core.*; do
failed=true
printf "::group::"
gdb -ex 'thread apply all bt full' ./haproxy $file
echo "::endgroup::"
done
if [ "$failed" = true ]; then
exit 1;
fi

View file

@ -0,0 +1,33 @@
#
# special purpose CI: test against OpenSSL built in "no-deprecated" mode
# let us run those builds weekly
#
# for example, OpenWRT uses such OpenSSL builds (those builds are smaller)
#
#
# some details might be found at NL: https://www.mail-archive.com/haproxy@formilux.org/msg35759.html
# GH: https://github.com/haproxy/haproxy/issues/367
name: openssl no-deprecated
on:
schedule:
- cron: "0 0 * * 4"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install VTest
run: |
scripts/build-vtest.sh
- name: Compile HAProxy
run: |
make DEFINE="-DOPENSSL_API_COMPAT=0x10100000L -DOPENSSL_NO_DEPRECATED" -j3 CC=gcc ERR=1 TARGET=linux-glibc USE_OPENSSL=1
- name: Run VTest
run: |
make reg-tests VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel

View file

@ -1,66 +0,0 @@
#
# goodput,crosstraffic are not run on purpose, those tests are intended to bandwidth measurement, we currently do not want to use GitHub runners for that
#
name: QUIC Interop AWS-LC
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 2"
permissions:
contents: read
jobs:
combined-build-and-run:
runs-on: ubuntu-24.04
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v6
- name: Update Docker to the latest
uses: docker/setup-docker-action@v4
- name: Build Docker image
id: push
uses: docker/build-push-action@v6
with:
context: https://github.com/haproxytech/haproxy-qns.git
platforms: linux/amd64
build-args: |
SSLLIB=AWS-LC
tags: local:aws-lc
- name: Install tshark
run: |
sudo apt-get update
sudo apt-get -y install tshark
- name: Run
run: |
git clone https://github.com/quic-interop/quic-interop-runner
cd quic-interop-runner
pip install -r requirements.txt --break-system-packages
python run.py -j result.json -l logs-chrome -r haproxy=local:aws-lc -t "http3" -c chrome -s haproxy
python run.py -j result.json -l logs-picoquic -r haproxy=local:aws-lc -t "handshake,transfer,longrtt,chacha20,multiplexing,retry,resumption,zerortt,http3,blackhole,keyupdate,ecn,amplificationlimit,handshakeloss,transferloss,handshakecorruption,transfercorruption,ipv6,v2" -c picoquic -s haproxy
python run.py -j result.json -l logs-quic-go -r haproxy=local:aws-lc -t "handshake,transfer,longrtt,chacha20,multiplexing,retry,resumption,zerortt,http3,blackhole,keyupdate,ecn,amplificationlimit,handshakeloss,transferloss,handshakecorruption,transfercorruption,ipv6,v2" -c quic-go -s haproxy
python run.py -j result.json -l logs-ngtcp2 -r haproxy=local:aws-lc -t "handshake,transfer,longrtt,chacha20,multiplexing,retry,resumption,zerortt,http3,blackhole,keyupdate,ecn,amplificationlimit,handshakeloss,transferloss,handshakecorruption,transfercorruption,ipv6,v2" -c ngtcp2 -s haproxy
- name: Delete succeeded logs
if: ${{ failure() }}
run: |
for client in chrome picoquic quic-go ngtcp2; do
pushd quic-interop-runner/logs-${client}/haproxy_${client}
cat ../../result.json | jq -r '.results[][] | select(.result=="succeeded") | .name' | xargs rm -rf
popd
done
- name: Logs upload
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: logs
path: quic-interop-runner/logs*/
retention-days: 6

View file

@ -1,64 +0,0 @@
#
# goodput,crosstraffic are not run on purpose, those tests are intended to bandwidth measurement, we currently do not want to use GitHub runners for that
#
name: QUIC Interop LibreSSL
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 2"
permissions:
contents: read
jobs:
combined-build-and-run:
runs-on: ubuntu-24.04
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v6
- name: Update Docker to the latest
uses: docker/setup-docker-action@v4
- name: Build Docker image
id: push
uses: docker/build-push-action@v6
with:
context: https://github.com/haproxytech/haproxy-qns.git
platforms: linux/amd64
build-args: |
SSLLIB=LibreSSL
tags: local:libressl
- name: Install tshark
run: |
sudo apt-get update
sudo apt-get -y install tshark
- name: Run
run: |
git clone https://github.com/quic-interop/quic-interop-runner
cd quic-interop-runner
pip install -r requirements.txt --break-system-packages
python run.py -j result.json -l logs-picoquic -r haproxy=local:libressl -t "handshake,transfer,longrtt,chacha20,multiplexing,retry,http3,blackhole,amplificationlimit,handshakeloss,transferloss,handshakecorruption,transfercorruption,v2" -c picoquic -s haproxy
python run.py -j result.json -l logs-quic-go -r haproxy=local:libressl -t "handshake,transfer,longrtt,chacha20,multiplexing,retry,http3,blackhole,amplificationlimit,transferloss,transfercorruption,v2" -c quic-go -s haproxy
- name: Delete succeeded logs
if: ${{ failure() }}
run: |
for client in picoquic quic-go; do
pushd quic-interop-runner/logs-${client}/haproxy_${client}
cat ../../result.json | jq -r '.results[][] | select(.result=="succeeded") | .name' | xargs rm -rf
popd
done
- name: Logs upload
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: logs
path: quic-interop-runner/logs*/
retention-days: 6

View file

@ -1,74 +0,0 @@
#
# weekly run against modern QuicTLS branch, i.e. https://github.com/quictls/quictls
#
name: QuicTLS
on:
schedule:
- cron: "0 0 * * 4"
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v6
- name: Install apt dependencies
run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get --no-install-recommends -y install socat gdb
- name: Install QuicTLS
run: env QUICTLS_VERSION=main QUICTLS_URL=https://github.com/quictls/quictls scripts/build-ssl.sh
- name: Compile HAProxy
run: |
make -j$(nproc) ERR=1 CC=gcc TARGET=linux-glibc \
USE_QUIC=1 USE_OPENSSL=1 \
SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/" \
ARCH_FLAGS="-ggdb3 -fsanitize=address"
sudo make install
- name: Show HAProxy version
id: show-version
run: |
ldd $(which haproxy)
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-vtest
- name: Run VTest for HAProxy
id: vtest
run: |
make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
for folder in ${TMPDIR:-/tmp}/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done
exit 1
- name: Run Unit tests
id: unittests
run: |
make unit-tests
- name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
failed=false
shopt -s nullglob
for file in /tmp/core.*; do
failed=true
printf "::group::"
gdb -ex 'thread apply all bt full' ./haproxy $file
echo "::endgroup::"
done
if [ "$failed" = true ]; then
exit 1;
fi

View file

@ -19,11 +19,11 @@ jobs:
# generated by .github/matrix.py.
generate-matrix:
name: Generate Build Matrix
runs-on: ubuntu-slim
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Generate Build Matrix
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -42,12 +42,20 @@ jobs:
# Configure a short TMPDIR to prevent failures due to long unix socket
# paths.
TMPDIR: /tmp
# Force ASAN output into asan.log to make the output more readable.
ASAN_OPTIONS: log_path=asan.log
OT_CPP_VERSION: 1.6.0
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 100
- name: Setup coredumps
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
run: |
sudo sysctl -w fs.suid_dumpable=1
sudo sysctl kernel.core_pattern=/tmp/core.%h.%e.%t
#
# Github Action cache key cannot contain comma, so we calculate it based on job name
#
@ -56,10 +64,21 @@ jobs:
run: |
echo "key=$(echo ${{ matrix.name }} | sha256sum | awk '{print $1}')" >> $GITHUB_OUTPUT
#
# temporary hack
# should be revisited after https://github.com/actions/runner-images/issues/9491 is resolved
#
- name: Setup enthropy
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
run: |
sudo sysctl vm.mmap_rnd_bits=28
- name: Cache SSL libs
if: ${{ matrix.ssl && matrix.ssl != 'stock' && matrix.ssl != 'BORINGSSL=yes' && !contains(matrix.ssl, 'QUICTLS') }}
if: ${{ matrix.ssl && matrix.ssl != 'stock' && matrix.ssl != 'BORINGSSL=yes' && matrix.ssl != 'QUICTLS=yes' }}
id: cache_ssl
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: '~/opt/'
key: ssl-${{ steps.generate-cache-key.outputs.key }}
@ -67,27 +86,29 @@ jobs:
- name: Cache OpenTracing
if: ${{ contains(matrix.FLAGS, 'USE_OT=1') }}
id: cache_ot
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: '~/opt-ot/'
key: ${{ matrix.os }}-ot-${{ matrix.CC }}-${{ env.OT_CPP_VERSION }}-${{ contains(matrix.name, 'ASAN') }}
key: ot-${{ matrix.CC }}-${{ env.OT_CPP_VERSION }}-${{ contains(matrix.name, 'ASAN') }}
- name: Install apt dependencies
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get --no-install-recommends -y install \
${{ case(contains(matrix.FLAGS, 'USE_LUA=1'), 'liblua5.4-dev', '') }} \
${{ case(contains(matrix.FLAGS, 'USE_PCRE2=1'), 'libpcre2-dev', '') }} \
${{ case(contains(matrix.ssl, 'BORINGSSL=yes'), 'ninja-build', '') }} \
sudo apt-get update
sudo apt-get install -y \
liblua5.3-dev \
libpcre2-dev \
libsystemd-dev \
ninja-build \
socat \
gdb \
jose
gdb
- name: Install brew dependencies
if: ${{ startsWith(matrix.os, 'macos-') }}
run: |
brew install socat
brew install lua
- uses: ./.github/actions/setup-vtest
- name: Install VTest
run: |
scripts/build-vtest.sh
- name: Install SSL ${{ matrix.ssl }}
if: ${{ matrix.ssl && matrix.ssl != 'stock' && steps.cache_ssl.outputs.cache-hit != 'true' }}
run: env ${{ matrix.ssl }} scripts/build-ssl.sh
@ -110,19 +131,10 @@ jobs:
ERR=1 \
TARGET=${{ matrix.TARGET }} \
CC=${{ matrix.CC }} \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
${{ join(matrix.FLAGS, ' ') }} \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/"
sudo make install-bin
- name: Compile admin/halog/halog
run: |
make -j$(nproc) admin/halog/halog \
ERR=1 \
TARGET=${{ matrix.TARGET }} \
CC=${{ matrix.CC }} \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
DEBUG="-DDEBUG_STRICT -DDEBUG_MEMORY_POOLS -DDEBUG_POOL_INTEGRITY" \
${{ join(matrix.FLAGS, ' ') }} \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/"
sudo make install
- name: Show HAProxy version
id: show-version
run: |
@ -136,34 +148,43 @@ jobs:
fi
echo "::endgroup::"
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
echo "version=$(haproxy -v |awk 'NR==1{print $3}')" >> $GITHUB_OUTPUT
- name: Install problem matcher for VTest
# This allows one to more easily see which tests fail.
run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest for HAProxy ${{ steps.show-version.outputs.version }}
id: vtest
run: |
make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel
# This is required for macOS which does not actually allow to increase
# the '-n' soft limit to the hard limit, thus failing to run.
ulimit -n 65536
ulimit -c unlimited
make reg-tests HAPROXY_ARGS="-dI" VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Config syntax check memleak smoke testing
if: ${{ contains(matrix.name, 'ASAN') }}
run: |
./haproxy -dI -f .github/h2spec.config -c
./haproxy -dI -f examples/content-sw-sample.cfg -c
./haproxy -dI -f examples/option-http_proxy.cfg -c
./haproxy -dI -f examples/quick-test.cfg -c
./haproxy -dI -f examples/transparent_proxy.cfg -c
- name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
for folder in ${TMPDIR:-/tmp}/haregtests-*/vtc.*; do
for folder in ${TMPDIR}/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done
exit 1
- name: Run Unit tests
id: unittests
run: |
make unit-tests
- name: Show Unit-Tests results
if: ${{ failure() && steps.unittests.outcome == 'failure' }}
run: |
for result in ${TMPDIR:-/tmp}/ha-unittests-*/results/res.*; do
printf "::group::"
cat $result
shopt -s nullglob
for asan in asan.log*; do
echo "::group::$asan"
cat $asan
echo "::endgroup::"
done
exit 1
- name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
@ -178,105 +199,3 @@ jobs:
if [ "$failed" = true ]; then
exit 1;
fi
Test-musl:
name: Alpine+musl, ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: gcc
CC: gcc
FLAGS:
- ARCH_FLAGS='-ggdb3'
- USE_LUA=1
- LUA_INC=/usr/include/lua5.3
- LUA_LIB=/usr/lib/lua5.3
- USE_OPENSSL=1
- USE_PCRE2=1
- USE_PCRE2_JIT=1
- USE_PROMEX=1
fail-fast: false
container:
image: alpine:latest
options: --privileged --ulimit core=-1 --security-opt seccomp=unconfined
volumes:
- /tmp/core:/tmp/core
steps:
- name: Setup coredumps
run: |
echo '/tmp/core/core.%h.%e.%t' > /proc/sys/kernel/core_pattern
- uses: actions/checkout@v6
- name: Install dependencies
run: apk add gcc gdb make tar git python3 libc-dev linux-headers pcre-dev pcre2-dev openssl-dev lua5.3-dev grep socat curl musl-dbg lua5.3-dbg jose
- uses: ./.github/actions/setup-vtest
- name: Compile HAProxy with ${{ matrix.CC }}
run: |
echo "::group::Show compiler's version"
echo | ${{ matrix.CC }} -v
echo "::endgroup::"
echo "::group::Show platform specific defines"
echo | ${{ matrix.CC }} -dM -xc -E -
echo "::endgroup::"
make -j$(nproc) all \
ERR=1 \
TARGET=linux-musl \
CC=${{ matrix.CC }} \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
${{ join(matrix.FLAGS, ' ') }} \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/"
make install-bin
- name: Show HAProxy version
id: show-version
run: |
echo "::group::Show dynamic libraries."
if command -v ldd > /dev/null; then
# Linux
ldd $(which haproxy)
else
# macOS
otool -L $(which haproxy)
fi
echo "::endgroup::"
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
- name: Run VTest for HAProxy ${{ steps.show-version.outputs.version }}
id: vtest
run: |
make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
for folder in ${TMPDIR:-/tmp}/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done
exit 1
- name: Run Unit tests
id: unittests
run: |
make unit-tests
- name: Show Unit-Tests results
if: ${{ failure() && steps.unittests.outcome == 'failure' }}
run: |
for result in ${TMPDIR:-/tmp}/ha-unittests-*/results/res.*; do
printf "::group::"
cat $result
echo "::endgroup::"
done
exit 1
- name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
failed=false
ls /tmp/core/
for file in /tmp/core/core.*; do
failed=true
printf "::group::"
gdb -ex 'thread apply all bt full' ./haproxy $file
echo "::endgroup::"
done
if [ "$failed" = true ]; then
exit 1;
fi

View file

@ -18,7 +18,6 @@ jobs:
msys2:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
defaults:
run:
shell: msys2 {0}
@ -36,7 +35,7 @@ jobs:
- USE_THREAD=1
- USE_ZLIB=1
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
install: >-
@ -59,7 +58,7 @@ jobs:
ERR=1 \
TARGET=${{ matrix.TARGET }} \
CC=${{ matrix.CC }} \
DEBUG="-DDEBUG_POOL_INTEGRITY" \
DEBUG="-DDEBUG_STRICT -DDEBUG_MEMORY_POOLS -DDEBUG_POOL_INTEGRITY" \
${{ join(matrix.FLAGS, ' ') }}
- name: Show HAProxy version
id: show-version

View file

@ -1,80 +0,0 @@
name: WolfSSL
on:
schedule:
- cron: "0 0 * * 4"
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v6
- name: Install apt dependencies
run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get --no-install-recommends -y install socat gdb jose
- name: Install WolfSSL
run: env WOLFSSL_VERSION=git-master WOLFSSL_DEBUG=1 CFLAGS="-fsanitize=address -g" scripts/build-ssl.sh
- name: Compile HAProxy
run: |
make -j$(nproc) ERR=1 CC=gcc TARGET=linux-glibc \
USE_OPENSSL_WOLFSSL=1 USE_QUIC=1 \
SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/" \
ARCH_FLAGS="-ggdb3 -fsanitize=address"
sudo make install
- name: Show HAProxy version
id: show-version
run: |
ldd $(which haproxy)
haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-vtest
- name: Run VTest for HAProxy
id: vtest
run: |
make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Run Unit tests
id: unittests
run: |
make unit-tests
- name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
for folder in ${TMPDIR:-/tmp}/haregtests-*/vtc.*; do
printf "::group::"
cat $folder/INFO
cat $folder/LOG
echo "::endgroup::"
done
exit 1
- name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: |
failed=false
shopt -s nullglob
for file in /tmp/core.*; do
failed=true
printf "::group::"
gdb -ex 'thread apply all bt full' ./haproxy $file
echo "::endgroup::"
done
if [ "$failed" = true ]; then
exit 1;
fi
- name: Show Unit-Tests results
if: ${{ failure() && steps.unittests.outcome == 'failure' }}
run: |
for result in ${TMPDIR:-/tmp}/ha-unittests-*/results/res.*; do
printf "::group::"
cat $result
echo "::endgroup::"
done
exit 1

1
.gitignore vendored
View file

@ -57,4 +57,3 @@ dev/udp/udp-perturb
/src/dlmalloc.c
/tests/test_hashes
doc/lua-api/_build
dev/term_events/term_events

View file

@ -8,7 +8,7 @@ branches:
env:
global:
- FLAGS="USE_LUA=1 USE_OPENSSL=1 USE_PCRE=1 USE_PCRE_JIT=1 USE_ZLIB=1"
- FLAGS="USE_LUA=1 USE_OPENSSL=1 USE_PCRE=1 USE_PCRE_JIT=1 USE_SYSTEMD=1 USE_ZLIB=1"
- TMPDIR=/tmp
addons:

View file

@ -171,17 +171,7 @@ feedback for developers:
as the previous releases that had 6 months to stabilize. In terms of
stability it really means that the point zero version already accumulated
6 months of fixes and that it is much safer to use even just after it is
released. There is one exception though, features marked as "experimental"
are not guaranteed to be maintained beyond the release of the next LTS
branch. The rationale here is that the experimental status is made to
expose an early preview of a feature, that is often incomplete, not always
in its definitive form regarding configuration, and for which developers
are seeking feedback from the users. It is even possible that changes will
be brought within the stable branch and it may happen that the feature
breaks. It is not imaginable to always be able to backport bug fixes too
far in this context since the code and configuration may change quite a
bit. Users who want to try experimental features are expected to upgrade
quickly to benefit from the improvements made to that feature.
released.
- for developers, given that the odd versions are solely used by highly
skilled users, it's easier to get advanced traces and captures, and there

4744
CHANGELOG

File diff suppressed because it is too large Load diff

View file

@ -1010,7 +1010,7 @@ you notice you're already practising some of them:
- continue to send pull requests after having been explained why they are not
welcome.
- give wrong advice to people asking for help, or sending them patches to
- give wrong advices to people asking for help, or sending them patches to
try which make no sense, waste their time, and give them a bad impression
of the people working on the project.

399
INSTALL
View file

@ -9,7 +9,7 @@ used to follow updates then it is recommended that instead you use the packages
provided by your software vendor or Linux distribution. Most of them are taking
this task seriously and are doing a good job at backporting important fixes.
If for any reason you would prefer a different version than the one packaged
If for any reason you'd prefer to use a different version than the one packaged
for your system, you want to be certain to have all the fixes or to get some
commercial support, other choices are available at http://www.haproxy.com/.
@ -34,26 +34,18 @@ are a few build examples :
- recent Linux system with all options, make and install :
$ make clean
$ make -j $(nproc) TARGET=linux-glibc \
USE_OPENSSL=1 USE_QUIC=1 USE_QUIC_OPENSSL_COMPAT=1 \
USE_LUA=1 USE_PCRE2=1
USE_OPENSSL=1 USE_LUA=1 USE_PCRE2=1 USE_SYSTEMD=1
$ sudo make install
- FreeBSD + OpenSSL, build with all options :
$ gmake -j $(sysctl -n hw.ncpu) TARGET=freebsd \
USE_OPENSSL=1 USE_QUIC=1 USE_QUIC_OPENSSL_COMPAT=1 \
USE_LUA=1 USE_PCRE2=1
- OpenBSD + LibreSSL, build with all options :
$ gmake -j $(sysctl -n hw.ncpu) TARGET=openbsd \
USE_OPENSSL=1 USE_QUIC=1 USE_LUA=1 USE_PCRE2=1
- FreeBSD and OpenBSD, build with all options :
$ gmake -j 4 TARGET=freebsd USE_OPENSSL=1 USE_LUA=1 USE_PCRE2=1
- embedded Linux, build using a cross-compiler :
$ make -j $(nproc) TARGET=linux-glibc USE_OPENSSL=1 USE_PCRE2=1 \
CC=/opt/cross/gcc730-arm/bin/gcc CFLAGS="-mthumb" ADDLIB=-latomic
CC=/opt/cross/gcc730-arm/bin/gcc ADDLIB=-latomic
- Build with static PCRE on Solaris / UltraSPARC :
$ make -j $(/usr/sbin/psrinfo -p) TARGET=solaris \
CPU_CFLAGS="-mcpu=v9" USE_STATIC_PCRE2=1
$ make TARGET=solaris CPU=ultrasparc USE_STATIC_PCRE2=1
For more advanced build options or if a command above reports an error, please
read the following sections.
@ -81,10 +73,10 @@ can use a relatively similar one and adjust specific variables by hand.
Most configuration variables are in fact booleans. Some options are detected and
enabled by default if available on the target platform. This is the case for all
those named "USE_<feature>". These booleans are enabled by "USE_<feature>=1"
and are disabled by "USE_<feature>=" (with no value) or "USE_<feature>=0". An
exhaustive list of the supported USE_* features is located at the top of the
main Makefile. The last occurrence of such an option on the command line
overrides any previous one. Example :
and are disabled by "USE_<feature>=" (with no value). An exhaustive list of the
supported USE_* features is located at the top of the main Makefile. The last
occurrence of such an option on the command line overrides any previous one.
Example :
$ make TARGET=generic USE_THREAD=
@ -111,22 +103,20 @@ HAProxy requires a working GCC or Clang toolchain and GNU make :
may want to retry with "gmake" which is the name commonly used for GNU make
on BSD systems.
- GCC >= 4.7 (up to 15 tested). Older versions are no longer supported due to
the latest mt_list update which only uses c11-like atomics. Newer versions
may sometimes break due to compiler regressions or behaviour changes. The
version shipped with your operating system is very likely to work with no
trouble. Clang >= 3.0 is also known to work as an alternative solution, and
versions up to 19 were successfully tested. Recent versions may emit a bit
more warnings that are worth reporting as they may reveal real bugs. TCC
(https://repo.or.cz/tinycc.git) is also usable for developers but will not
support threading and was found at least once to produce bad code in some
rare corner cases (since fixed). But it builds extremely quickly (typically
half a second for the whole project) and is very convenient to run quick
tests during API changes or code refactoring.
- GCC >= 4.2 (up to 13 tested). Older versions can be made to work with a
few minor adaptations if really needed. Newer versions may sometimes break
due to compiler regressions or behaviour changes. The version shipped with
your operating system is very likely to work with no trouble. Clang >= 3.0
is also known to work as an alternative solution. Recent versions may emit
a bit more warnings that are worth reporting as they may reveal real bugs.
TCC (https://repo.or.cz/tinycc.git) is also usable for developers but will
not support threading and was found at least once to produce bad code in
some rare corner cases (since fixed). But it builds extremely quickly
(typically half a second for the whole project) and is very convenient to
run quick tests during API changes or code refactoring.
- GNU ld (binutils package), with no particular version. Other linkers might
work but were not tested. The default one from your operating system will
normally work.
work but were not tested.
On debian or Ubuntu systems and their derivatives, you may get all these tools
at once by issuing the two following commands :
@ -237,7 +227,7 @@ to forcefully enable it using "USE_LIBCRYPT=1".
-----------------
For SSL/TLS, it is necessary to use a cryptography library. HAProxy currently
supports the OpenSSL library, and is known to build and work with branches
1.0.0, 1.0.1, 1.0.2, 1.1.0, 1.1.1, and 3.0 to 3.6. It is recommended to use
1.0.0, 1.0.1, 1.0.2, 1.1.0, 1.1.1, 3.0, 3.1 and 3.2. It is recommended to use
at least OpenSSL 1.1.1 to have support for all SSL keywords and configuration
in HAProxy. OpenSSL follows a long-term support cycle similar to HAProxy's,
and each of the branches above receives its own fixes, without forcing you to
@ -254,20 +244,16 @@ https://github.com/openssl/openssl/issues/17627). If a migration to 3.x is
mandated by support reasons, at least 3.1 recovers a small fraction of this
important loss.
Three OpenSSL derivatives called LibreSSL, QUICTLS, and AWS-LC are
Four OpenSSL derivatives called LibreSSL, BoringSSL, QUICTLS, and AWS-LC are
reported to work as well. While there are some efforts from the community to
ensure they work well, OpenSSL remains the primary target and this means that
in case of conflicting choices, OpenSSL support will be favored over other
options. Note that QUIC is not fully supported when haproxy is built with
OpenSSL < 3.5.2 version. In this case, QUICTLS or AWS-LC are the preferred
alternatives. As of writing this, the QuicTLS project follows OpenSSL very
closely and provides update simultaneously, but being a volunteer-driven
project, its long-term future does not look certain enough to convince
operating systems to package it, so it needs to be build locally. Recent
versions of AWS-LC (>= 1.22 and the FIPS branches) are pretty complete and
generally more performant than other OpenSSL derivatives, but may behave
slightly differently, particularly when dealing with outdated setups. See
the section about QUIC in this document.
OpenSSL. In this case, QUICTLS is the preferred alternative. As of writing
this, the QuicTLS project follows OpenSSL very closely and provides update
simultaneously, but being a volunteer-driven project, its long-term future does
not look certain enough to convince operating systems to package it, so it
needs to be build locally. See the section about QUIC in this document.
A fifth option is wolfSSL (https://github.com/wolfSSL/wolfssl). It is the only
supported alternative stack not based on OpenSSL, yet which implements almost
@ -326,7 +312,7 @@ command line, for example:
$ make -j $(nproc) TARGET=generic USE_OPENSSL_WOLFSSL=1 USE_QUIC=1 \
SSL_INC=/opt/wolfssl-5.6.6/include SSL_LIB=/opt/wolfssl-5.6.6/lib
To use HAProxy with AWS-LC you must have version v1.22.0 or newer of AWS-LC
To use HAProxy with AWS-LC you must have version v1.13.0 or newer of AWS-LC
built and installed locally.
$ cd ~/build/aws-lc
$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/aws-lc
@ -389,15 +375,10 @@ systems, by passing "USE_SLZ=" to the "make" command.
Please note that SLZ will benefit from some CPU-specific instructions like the
availability of the CRC32 extension on some ARM processors. Thus it can further
improve its performance to build with:
- "CPU_CFLAGS=-march=native" on the target system or
- "CPU_CFLAGS=-march=armv81" on modern systems such as Graviton2 or A55/A75
and beyond)
- "CPU_CFLAGS=-march=a72" (e.g. for RPi4, or AWS Graviton)
- "CPU_CFLAGS=-march=a53" (e.g. for RPi3)
- "CPU_CFLAGS=-march=armv8-auto" automatic detection with minor runtime
penalty)
improve its performance to build with "CPU=native" on the target system, or
"CPU=armv81" (modern systems such as Graviton2 or A55/A75 and beyond),
"CPU=a72" (e.g. for RPi4, or AWS Graviton), "CPU=a53" (e.g. for RPi3), or
"CPU=armv8-auto" (automatic detection with minor runtime penalty).
A second option involves the widely known zlib library, which is very likely
installed on your system. In order to use zlib, simply pass "USE_ZLIB=1" to the
@ -471,6 +452,12 @@ are the extra libraries that may be referenced at build time :
on Linux. It is automatically detected and may be disabled
using "USE_DL=", though it should never harm.
- USE_SYSTEMD=1 enables support for the sdnotify features of systemd,
allowing better integration with systemd on Linux systems
which come with it. It is never enabled by default so there
is no need to disable it.
4.10) Common errors
-------------------
Some build errors may happen depending on the options combinations or the
@ -494,8 +481,8 @@ target. Common issues may include:
other supported compatible library.
- many "dereferencing pointer 'sa.985' does break strict-aliasing rules"
=> these warnings happen on old compilers (typically gcc before 7.x),
and may safely be ignored; newer ones are better on these.
=> these warnings happen on old compilers (typically gcc-4.4), and may
safely be ignored; newer ones are better on these.
4.11) QUIC
@ -504,11 +491,10 @@ QUIC is the new transport layer protocol and is required for HTTP/3. This
protocol stack is currently supported as an experimental feature in haproxy on
the frontend side. In order to enable it, use "USE_QUIC=1 USE_OPENSSL=1".
Note that QUIC is not always fully supported by the OpenSSL library depending on
its version. Indeed QUIC 0-RTT cannot be supported by OpenSSL for versions before
3.5 contrary to others libraries with full QUIC support. The preferred option is
to use QUICTLS. This is a fork of OpenSSL with a QUIC-compatible API. Its
repository is available at this location:
Note that QUIC is not fully supported by the OpenSSL library. Indeed QUIC 0-RTT
cannot be supported by OpenSSL contrary to others libraries with full QUIC
support. The preferred option is to use QUICTLS. This is a fork of OpenSSL with
a QUIC-compatible API. Its repository is available at this location:
https://github.com/quictls/openssl
@ -536,18 +522,14 @@ way assuming that wolfSSL was installed in /opt/wolfssl-5.6.0 as shown in 4.5:
SSL_INC=/opt/wolfssl-5.6.0/include SSL_LIB=/opt/wolfssl-5.6.0/lib
LDFLAGS="-Wl,-rpath,/opt/wolfssl-5.6.0/lib"
As last resort, haproxy may be compiled against OpenSSL as follows from 3.5
version with 0-RTT support:
$ make TARGET=generic USE_OPENSSL=1 USE_QUIC=1
or as follows for all OpenSSL versions but without O-RTT support:
As last resort, haproxy may be compiled against OpenSSL as follows:
$ make TARGET=generic USE_OPENSSL=1 USE_QUIC=1 USE_QUIC_OPENSSL_COMPAT=1
In addition to this requirements, the QUIC listener bindings must be explicitly
enabled with a specific QUIC tuning parameter. (see "limited-quic" global
parameter of haproxy Configuration Manual).
Note that QUIC 0-RTT is not supported by haproxy QUIC stack when built against
OpenSSL. In addition to this compilation requirements, the QUIC listener
bindings must be explicitly enabled with a specific QUIC tuning parameter.
(see "limited-quic" global parameter of haproxy Configuration Manual).
5) How to build HAProxy
@ -555,17 +537,13 @@ parameter of haproxy Configuration Manual).
This section assumes that you have already read section 2 (basic principles)
and section 3 (build environment). It often refers to section 4 (dependencies).
It goes into more details with the main options.
5.1) Configuring the TARGET
---------------------------
To build haproxy, you have to choose your target OS amongst the following ones
and assign it to the TARGET variable :
- linux-glibc for Linux kernel 4.17 and above
- linux-glibc for Linux kernel 2.6.28 and above
- linux-glibc-legacy for Linux kernel 2.6.28 and above without new features
- linux-musl for Linux kernel 4.17 and above with musl libc
- linux-musl for Linux kernel 2.6.28 and above with musl libc
- solaris for Solaris 10 and above
- freebsd for FreeBSD 10 and above
- dragonfly for DragonFlyBSD 4.3 and above
@ -580,64 +558,29 @@ and assign it to the TARGET variable :
- generic for any other OS or version.
- custom to manually adjust every setting
Example:
$ make -j $(nproc) TARGET=linux-glibc
You may also choose your CPU to benefit from some optimizations. This is
particularly important on UltraSparc machines. For this, you can assign
one of the following choices to the CPU variable :
AIX 5.3 is known to work with the generic target. However, for the binary to
also run on 5.2 or earlier, you need to build with DEFINE="-D_MSGQSUPPORT",
otherwise __fd_select() will be used while not being present in the libc, but
this is easily addressed using the "aix52" target. If you get build errors
because of strange symbols or section mismatches, simply remove -g from
ARCH_FLAGS.
- i686 for intel PentiumPro, Pentium 2 and above, AMD Athlon (32 bits)
- i586 for intel Pentium, AMD K6, VIA C3.
- ultrasparc : Sun UltraSparc I/II/III/IV processor
- power8 : IBM POWER8 processor
- power9 : IBM POWER9 processor
- armv81 : modern ARM cores (Cortex A55/A75/A76/A78/X1, Neoverse, Graviton2)
- a72 : ARM Cortex-A72 or A73 (e.g. RPi4, Odroid N2, AWS Graviton)
- a53 : ARM Cortex-A53 or any of its successors in 64-bit mode (e.g. RPi3)
- armv8-auto : support both older and newer armv8 cores with a minor penalty,
thanks to gcc 10's outline atomics (default with gcc 10.2).
- native : use the build machine's specific processor optimizations. Use with
extreme care, and never in virtualized environments (known to break).
- generic : any other processor or no CPU-specific optimization. (default)
Building on AIX 7.2 works fine using the "aix72-gcc" TARGET. It adds two
special CFLAGS to prevent the loading of AIX's xmem.h and var.h. This is done
by defining the corresponding include-guards _H_XMEM and _H_VAR. Without
excluding those header-files the build fails because of redefinition errors.
Furthermore, the atomic library is added to the LDFLAGS to allow for
multithreading via USE_THREAD.
You can easily define your own target with the GNU Makefile. Unknown targets
are processed with no default option except USE_POLL=default. So you can very
well use that property to define your own set of options. USE_POLL and USE_SLZ
can even be disabled by setting them to an empty string or a zero. For
example :
$ gmake TARGET=tiny USE_POLL="" USE_SLZ=0 TARGET_CFLAGS=-fomit-frame-pointer
5.2) Adding extra CFLAGS for compiling
--------------------------------------
A generic CFLAGS variable may be set to append any option to pass to the C
compiler. These flags are passed last so the variable may be used to override
other options such as warnings, optimization levels, include paths etc.
A default optimization level of -O2 is set by variable OPT_CFLAGS which may be
overridden if desired. It's used early in the list of CFLAGS so that any other
set of CFLAGS providing a different value may easily override it.
Some platforms may benefit from some CPU-specific options that will enable
certain instruction sets, word size or endianness for example. One of them is
the common "-march=native" that indicates to modern compilers that they need to
optimize for the machine the compiler is running on. Such options may be either
passed in the CPU_CFLAGS or in the CFLAGS variable, either will work though
one may be more convenient for certain methods of packaging and the other one
for other methods. Among the many possible options, the following ones are
known for having successfully been used:
- "-march=native" for a native build
- "-march=armv8-a+crc" for older ARM Cortex A53/A72/A73 (such as RPi 3B/4B)
- "-march=armv8.1-a" for modern ARM Cortex A55/A76, Graviton2+, RPi 5
- "-march=armv8-a+crc -moutline-atomics" to support older ARM with better
support of modern cores with gcc-10+
- "-mavx", "-mavx2", "-mavx512", to enable certain x86 SIMD instruction sets
- "-march=i586" to support almost all 32-bit x86 systems
- "-march=i686" to support only the latest 32-bit x86 systems
- "-march=i386" to support even the oldest 32-bit x86 systems
- "-mlittle-endian -march=armv5te" for some little-endian ARMv5 systems
- "-mcpu=v9 -mtune=ultrasparc -m64" for a 64-bit Solaris SPARC build
- "-march=1004kc -mtune=1004kc" for some multi-core 32-bit MIPS 1004Kc
- "-march=24kc -mtune=24kc" for some single-core 32-bit MIPS 24Kc
Alternatively, you may just set the CPU_CFLAGS value to the optimal GCC options
for your platform. A second variable named SMALL_OPTS also supports passing a
number of defines and compiler options usually for small systems. For better
clarity it's recommended to pass the options which result in a smaller binary
(like memory limits or -Os) into this variable.
If you are building for a different system than the one you're building on,
this is called "cross-compiling". HAProxy supports cross-compilation pretty
@ -655,49 +598,20 @@ flags are passed to the compiler nor what compiler is involved. Simply append
details again. It is recommended to use this option when cross-compiling to
verify that the paths are correct and that /usr/include is never involved.
If you need to pass some defines to the preprocessor or compiler, you may pass
them all in the DEFINE variable. Example:
$ make TARGET=generic DEFINE="-DDEBUG_DONT_SHARE_POOLS"
The ADDINC variable may be used to add some extra include paths; this is
sometimes needed when cross-compiling. Similarly the ADDLIB variable may be
used to specify extra paths to library files. Example :
$ make TARGET=generic ADDINC=-I/opt/cross/include ADDLIB=-L/opt/cross/lib64
5.3) Adding extra LDFLAGS for linking
-------------------------------------
If a particular target requires specific link-time flags, these can be passed
via the LDFLAGS variable. This variable is passed to the linker immediately
after ARCH_FLAGS. One of the common use cases is to add some run time search
paths for a dynamic library that's not part of the default system search path:
$ make -j $(nproc) TARGET=generic USE_OPENSSL_AWSLC=1 USE_QUIC=1 \
SSL_INC=/opt/aws-lc/include SSL_LIB=/opt/aws-lc/lib \
LDFLAGS="-Wl,-rpath,/opt/aws-lc/lib"
Some options require to be consistent between the compilation stage and the
linking stage. This is the case for options which enable debugging (e.g. "-g"),
profiling ("-pg"), link-time optimization ("-flto"), endianness ("-EB", "-EL"),
bit width ("-m32", "-m64"), or code analyzers ("-fsanitize=address"). These
options can be passed via the ARCH_FLAGS variable, which will be used at both
stages during the build process, thus avoiding the risk of inconsistencies. By
default, ARCH_FLAGS only contains "-g" to enable the generation of debug
symbols. For example, in order to build a 32-bit binary on an x86_64 Linux
system with SSL support without support for compression but when OpenSSL
You may want to build specific target binaries which do not match your native
compiler's target. This is particularly true on 64-bit systems when you want
to build a 32-bit binary. Use the ARCH variable for this purpose. Right now
it only knows about a few x86 variants (i386,i486,i586,i686,x86_64), two
generic ones (32,64) and sets -m32/-m64 as well as -march=<arch> accordingly.
This variable is only used to set ARCH_FLAGS to preset values, so if you know
the arch-specific flags that your system needs, you may prefer to set
ARCH_FLAGS instead. Note that these flags are passed both to the compiler and
to the linker. For example, in order to build a 32-bit binary on an x86_64
Linux system with SSL support without support for compression but when OpenSSL
requires ZLIB anyway :
$ make TARGET=linux-glibc ARCH_FLAGS="-m32 -g" USE_OPENSSL=1 ADDLIB=-lz
$ make TARGET=linux-glibc ARCH=i386 USE_OPENSSL=1 ADDLIB=-lz
and building with the address sanitizer (ASAN) simply requires:
$ make TARGET=linux-glibc ARCH_FLAGS="-fsanitize=address -g"
5.4) Other common OS-specific options
-------------------------------------
Recent systems can resolve IPv6 host names using getaddrinfo(). This primitive
is not present in all libcs and does not work in all of them either. Support in
glibc was broken before 2.3. Some embedded libs may not properly work either,
@ -724,63 +638,16 @@ section 4 about dependencies for more information on how to build with OpenSSL.
HAProxy can compress HTTP responses to save bandwidth. Please see section 4
about dependencies to see the available libraries and associated options.
If you need to pass other defines, includes, libraries, etc... then please
check the Makefile to see which ones will be available in your case, and
use/override the USE_* variables from the Makefile.
By default, the DEBUG_CFLAGS variable is set to '-g' to enable debug symbols.
It is not wise to disable it on uncommon systems, because it's often the only
way to get a usable core when you need one. Otherwise, you can set DEBUG to
'-s' to strip the binary.
If the ERR variable is set to any non-empty value, then -Werror will be added
to the compiler so that any build warning will trigger an error. This is the
recommended way to build when developing, and it is expected that contributed
patches were tested with ERR=1.
5.5) Adjusting the build error / warning behavior
-------------------------------------------------
If the ERR variable is set to any non-empty value other than "0", then -Werror
will be added to the compiler so that any build warning will trigger an error.
This is the recommended way to build when developing, and it is expected that
contributed patches were tested with ERR=1. Similarly, for developers, another
variable, FAILFAST enables -Wfatal-errors when set to non-empty except 0, and
makes the compiler stop at the first error instead of scrolling pages. It's
essentially a matter of taste.
Packagers who want to achieve the cleanest warning-free builds may be
interested in knowing that all enabled warnings are normally placed into
the WARN_CFLAGS variable. The variable contains a list of pre-established
warnings and a list of some that are dynamically detected on the compiler.
If the build environment or toolchain doesn't even support some of the basic
ones, it is then possible to just redefine them by passing the main ones in
WARN_CFLAGS (e.g. at the very least -W -Wall). Similarly, it may sometimes
be desirable not to disable certain warnings when porting to new platforms
or during code audits, or simply because the toolchain doesn't support some
of the most basic -Wno options. In this case, the list of automatic -Wno
variables is specified by variable NOWARN_CFLAGS, which is passed after
WARN_CFLAGS (i.e. it can undo some of the WARN_CFLAGS settings). Be careful
with it, as clearing this list can yield many warnings depending on the
compiler and options.
The DEP variable is automatically set to the list of include files and also
designates a file that contains the last build options used. It is used during
the build process to compute dependencies and decide whether or not to rebuild
everything (we do rebuild everything when .h files are touched or when build
options change). Sometimes when performing fast build iterations on inline
functions it may be desirable to avoid a full rebuild. Forcing this variable
to be empty will be sufficient to achieve this. This variable must never be
forced to produce final binaries, and must not be used during bisect sessions,
as it will often lead to the wrong commit.
Examples:
# silence strict-aliasing warnings with old gcc-5.5:
$ make -j$(nproc) TARGET=linux-glibc CC=gcc-55 CFLAGS=-fno-strict-aliasing
# disable all warning options:
$ make -j$(nproc) TARGET=linux-glibc CC=mycc WARN_CFLAGS= NOWARN_CFLAGS=
# enable -Werror and -Wfatal-errors to immediately stop on error
$ make -j$(nproc) TARGET=linux-glibc ERR=1 FAILFAST=1
# try to restart the build where it was after hacking an include file, to
# check if that was sufficient or not:
$ make -j$(nproc) TARGET=linux-glibc ERR=1 DEP=
5.6) Enabling a DEBUG build
---------------------------
The DEBUG variable is used to extend the CFLAGS and is preset to a list of
build-time options that are known for providing significant reliability
improvements and a barely perceptible performance cost. Unless instructed to do
@ -791,8 +658,8 @@ these options should not be changed. Among the usable ones are:
conditions are not met, and whose violation will result in a misbehaving
process due to memory corruption or other significant trouble, possibly
caused by an attempt to exploit a bug in the program or a library it relies
on. The option knows 3 values: 0 (disable all such assertions, not
recommended), 1 (enable all inexpensive assertions, the default), and
on. The option knows 3 values: 0 (disable all such assertions, the default
when the option is not set), 1 (enable all inexpensive assertions), and
2 (enable all assertions even in fast paths). Setting the option with no
value corresponds to 1, which is the recommended value for production.
@ -824,7 +691,7 @@ these options should not be changed. Among the usable ones are:
overflows, which may have security implications. The cost is extremely low
(less than 1% increase in memory footprint). This is equivalent to adding
"-dMtag" on the command line. This option is enabled in the default build
options and may be disabled with -DDEBUG_MEMORY_POOLS=0.
options.
- -DDEBUG_DONT_SHARE_POOLS: this will keep separate pools for same-sized
objects of different types. Using this increases the memory usage a little
@ -844,34 +711,58 @@ these options should not be changed. Among the usable ones are:
are encouraged to use it, in combination with -DDEBUG_DONT_SHARE_POOLS and
-DDEBUG_MEMORY_POOLS, as this could catch dangerous regressions.
As such, "-DDEBUG_STRICT -DDEBUG_MEMORY_POOLS" is implicit and recommended for
production. For security sensitive environments, it is recommended to use
"-DDEBUG_STRICT_ACTION=2 -DDEBUG_DONT_SHARE_POOLS". When testing new versions
or trying to nail a bug down, use "-DDEBUG_STRICT=2 -DDEBUG_STRICT_ACTION=2 \
-DDEBUG_DONT_SHARE_POOLS -DDEBUG_POOL_INTEGRITY". Finally in order to minimize
memory usage by disabling these integrity features, it is also possible to use
"-DDEBUG_STRICT=0 -DDEBUG_MEMORY_POOLS=0".
As such, for regular production, "-DDEBUG_STRICT -DDEBUG_MEMORY_POOLS" is
recommended. For security sensitive environments, it is recommended to use
"-DDEBUG_STRICT -DDEBUG_STRICT_ACTION=2 -DDEBUG_MEMORY_POOLS \
-DDEBUG_DONT_SHARE_POOLS". For deployments dedicated to testing new versions or
when trying to nail a bug down, use "-DDEBUG_STRICT=2 -DDEBUG_STRICT_ACTION=2 \
-DDEBUG_MEMORY_POOLS -DDEBUG_DONT_SHARE_POOLS -DDEBUG_POOL_INTEGRITY".
The DEP variable is automatically set to the list of include files and also
designates a file that contains the last build options used. It is used during
the build process to compute dependencies and decide whether or not to rebuild
everything (we do rebuild everything when .h files are touched or when build
options change). Sometimes when performing fast build iterations on inline
functions it may be desirable to avoid a full rebuild. Forcing this variable
to be empty will be sufficient to achieve this. This variable must never be
forced to produce final binaries, and must not be used during bisect sessions,
as it will often lead to the wrong commit.
5.7) Summary of the Makefile's main variables
---------------------------------------------
If you need to pass other defines, includes, libraries, etc... then please
check the Makefile to see which ones will be available in your case, and
use/override the USE_* variables from the Makefile.
The following variables are commonly used:
- TARGET platform name, empty by default, see help
- CC path to the C compiler, defaults to "cc"
- LD path to the linker, defaults to "$CC"
- CFLAGS CFLAGS to append at the end, empty by default
- LDFLAGS LDFLAGS to append at the end, empty by default
- ARCH_FLAGS flags common to CC and LD (-fsanitize, etc). Defaults to "-g"
- OPT_CFLAGS C compiler optimization level. Defaults to "-O2"
- WARN_CFLAGS list of autodetected C compiler warnings to enable
- NOWARN_CFLAGS list of autodetected C compiler warnings to disable
- ADDINC include directives to append at the end, empty by default
- ADDLIB lib directives to append at the end, empty by default
- DEFINE extra macros definitions for compiler, empty by default
- DEBUG extra DEBUG options for compiler, empty by default
- ERR enables -Werror if non-zero, empty by default
- FAILFAST enables -Wfatal-error if non-zero, empty by default
AIX 5.3 is known to work with the generic target. However, for the binary to
also run on 5.2 or earlier, you need to build with DEFINE="-D_MSGQSUPPORT",
otherwise __fd_select() will be used while not being present in the libc, but
this is easily addressed using the "aix52" target. If you get build errors
because of strange symbols or section mismatches, simply remove -g from
DEBUG_CFLAGS.
Building on AIX 7.2 works fine using the "aix72-gcc" TARGET. It adds two
special CFLAGS to prevent the loading of AIX's xmem.h and var.h. This is done
by defining the corresponding include-guards _H_XMEM and _H_VAR. Without
excluding those header-files the build fails because of redefinition errors.
Furthermore, the atomic library is added to the LDFLAGS to allow for
multithreading via USE_THREAD.
You can easily define your own target with the GNU Makefile. Unknown targets
are processed with no default option except USE_POLL=default. So you can very
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. For example :
$ gmake TARGET=tiny USE_POLL="" USE_SLZ="" TARGET_CFLAGS=-fomit-frame-pointer
If you need to pass some defines to the preprocessor or compiler, you may pass
them all in the DEFINE variable. Example:
$ make TARGET=generic DEFINE="-DDEBUG_DONT_SHARE_POOLS -DDEBUG_MEMORY_POOLS"
The ADDINC variable may be used to add some extra include paths; this is
sometimes needed when cross-compiling. Similarly the ADDLIB variable may be
used to specify extra paths to library files. Example :
$ make TARGET=generic ADDINC=-I/opt/cross/include ADDLIB=-L/opt/cross/lib64
6) How to install HAProxy

View file

@ -138,7 +138,7 @@ ScientiaMobile WURFL Device Detection
Maintainer: Paul Borile, Massimiliano Bellomi <wurfl-haproxy-support@scientiamobile.com>
Files: addons/wurfl, doc/WURFL-device-detection.txt
SPOE
SPOE (deprecated)
Maintainer: Christopher Faulet <cfaulet@haproxy.com>
Files: src/flt_spoe.c, include/haproxy/spoe*.h, doc/SPOE.txt

569
Makefile

File diff suppressed because it is too large Load diff

22
README Normal file
View file

@ -0,0 +1,22 @@
The HAProxy documentation has been split into a number of different files for
ease of use.
Please refer to the following files depending on what you're looking for :
- INSTALL for instructions on how to build and install HAProxy
- BRANCHES to understand the project's life cycle and what version to use
- LICENSE for the project's license
- CONTRIBUTING for the process to follow to submit contributions
The more detailed documentation is located into the doc/ directory :
- doc/intro.txt for a quick introduction on HAProxy
- doc/configuration.txt for the configuration's reference manual
- doc/lua.txt for the Lua's reference manual
- doc/SPOE.txt for how to use the SPOE engine
- doc/network-namespaces.txt for how to use network namespaces under Linux
- doc/management.txt for the management guide
- doc/regression-testing.txt for how to use the regression testing suite
- doc/peers.txt for the peers protocol reference
- doc/coding-style.txt for how to adopt HAProxy's coding style
- doc/internals for developer-specific documentation (not all up to date)

View file

@ -1,60 +0,0 @@
# HAProxy
[![AWS-LC](https://github.com/haproxy/haproxy/actions/workflows/aws-lc.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/aws-lc.yml)
[![Illumos](https://github.com/haproxy/haproxy/actions/workflows/illumos.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/illumos.yml)
[![NetBSD](https://github.com/haproxy/haproxy/actions/workflows/netbsd.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/netbsd.yml)
[![FreeBSD](https://api.cirrus-ci.com/github/haproxy/haproxy.svg?task=FreeBSD)](https://cirrus-ci.com/github/haproxy/haproxy/)
[![VTest](https://github.com/haproxy/haproxy/actions/workflows/vtest.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/vtest.yml)
![HAProxy logo](doc/HAProxyCommunityEdition_60px.png)
HAProxy is a free, very fast and reliable reverse-proxy offering high availability, load balancing, and proxying for TCP
and HTTP-based applications.
## Installation
The [INSTALL](INSTALL) file describes how to build HAProxy.
A [list of packages](https://github.com/haproxy/wiki/wiki/Packages) is also available on the wiki.
## Getting help
The [discourse](https://discourse.haproxy.org/) and the [mailing-list](https://www.mail-archive.com/haproxy@formilux.org/)
are available for questions or configuration assistance. You can also use the [slack](https://slack.haproxy.org/) or
[IRC](irc://irc.libera.chat/%23haproxy) channel. Please don't use the issue tracker for these.
The [issue tracker](https://github.com/haproxy/haproxy/issues/) is only for bug reports or feature requests.
## Documentation
The HAProxy documentation has been split into a number of different files for
ease of use. It is available in text format as well as HTML. The wiki is also meant to replace the old architecture
guide.
- [HTML documentation](http://docs.haproxy.org/)
- [HTML HAProxy LUA API Documentation](https://www.arpalert.org/haproxy-api.html)
- [Wiki](https://github.com/haproxy/wiki/wiki)
Please refer to the following files depending on what you're looking for:
- [INSTALL](INSTALL) for instructions on how to build and install HAProxy
- [BRANCHES](BRANCHES) to understand the project's life cycle and what version to use
- [LICENSE](LICENSE) for the project's license
- [CONTRIBUTING](CONTRIBUTING) for the process to follow to submit contributions
The more detailed documentation is located into the doc/ directory:
- [ doc/intro.txt ](doc/intro.txt) for a quick introduction on HAProxy
- [ doc/configuration.txt ](doc/configuration.txt) for the configuration's reference manual
- [ doc/lua.txt ](doc/lua.txt) for the Lua's reference manual
- [ doc/SPOE.txt ](doc/SPOE.txt) for how to use the SPOE engine
- [ doc/network-namespaces.txt ](doc/network-namespaces.txt) for how to use network namespaces under Linux
- [ doc/management.txt ](doc/management.txt) for the management guide
- [ doc/regression-testing.txt ](doc/regression-testing.txt) for how to use the regression testing suite
- [ doc/peers.txt ](doc/peers.txt) for the peers protocol reference
- [ doc/coding-style.txt ](doc/coding-style.txt) for how to adopt HAProxy's coding style
- [ doc/internals ](doc/internals) for developer-specific documentation (not all up to date)
## License
HAProxy is licensed under [GPL 2](doc/gpl.txt) or any later version, the headers under [LGPL 2.1](doc/lgpl.txt). See the
[LICENSE](LICENSE) file for a more detailed explanation.

View file

@ -1,2 +1,2 @@
$Format:%ci$
2026/04/03
2024/04/06

View file

@ -1 +1 @@
3.4-dev8
3.0-dev7

View file

@ -5,8 +5,7 @@ CXX := c++
CXXLIB := -lstdc++
ifeq ($(DEVICEATLAS_SRC),)
OPTIONS_CFLAGS += -I$(DEVICEATLAS_INC)
OPTIONS_LDFLAGS += -Wl,-rpath,$(DEVICEATLAS_LIB) -L$(DEVICEATLAS_LIB) -lda
OPTIONS_LDFLAGS += -lda
else
DEVICEATLAS_INC = $(DEVICEATLAS_SRC)
DEVICEATLAS_LIB = $(DEVICEATLAS_SRC)

View file

@ -31,7 +31,6 @@ static struct {
da_atlas_t atlas;
da_evidence_id_t useragentid;
da_severity_t loglevel;
size_t maxhdrlen;
char separator;
unsigned char daset:1;
} global_deviceatlas = {
@ -43,7 +42,6 @@ static struct {
.atlasmap = NULL,
.atlasfd = -1,
.useragentid = 0,
.maxhdrlen = 0,
.daset = 0,
.separator = '|',
};
@ -59,10 +57,6 @@ static int da_json_file(char **args, int section_type, struct proxy *curpx,
return -1;
}
global_deviceatlas.jsonpath = strdup(args[1]);
if (unlikely(global_deviceatlas.jsonpath == NULL)) {
memprintf(err, "deviceatlas json file : out of memory.\n");
return -1;
}
return 0;
}
@ -79,7 +73,6 @@ static int da_log_level(char **args, int section_type, struct proxy *curpx,
loglevel = atol(args[1]);
if (loglevel < 0 || loglevel > 3) {
memprintf(err, "deviceatlas log level : expects a log level between 0 and 3, %s given.\n", args[1]);
return -1;
} else {
global_deviceatlas.loglevel = (da_severity_t)loglevel;
}
@ -108,10 +101,6 @@ static int da_properties_cookie(char **args, int section_type, struct proxy *cur
return -1;
} else {
global_deviceatlas.cookiename = strdup(args[1]);
if (unlikely(global_deviceatlas.cookiename == NULL)) {
memprintf(err, "deviceatlas cookie name : out of memory.\n");
return -1;
}
}
global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
return 0;
@ -130,7 +119,6 @@ static int da_cache_size(char **args, int section_type, struct proxy *curpx,
cachesize = atol(args[1]);
if (cachesize < 0 || cachesize > DA_CACHE_MAX) {
memprintf(err, "deviceatlas cache size : expects a cache size between 0 and %d, %s given.\n", DA_CACHE_MAX, args[1]);
return -1;
} else {
#ifdef APINOCACHE
fprintf(stdout, "deviceatlas cache size : no-op, its support is disabled.\n");
@ -177,7 +165,7 @@ static int init_deviceatlas(void)
da_status_t status;
jsonp = fopen(global_deviceatlas.jsonpath, "r");
if (unlikely(jsonp == 0)) {
if (jsonp == 0) {
ha_alert("deviceatlas : '%s' json file has invalid path or is not readable.\n",
global_deviceatlas.jsonpath);
err_code |= ERR_ALERT | ERR_FATAL;
@ -189,11 +177,9 @@ static int init_deviceatlas(void)
status = da_atlas_compile(jsonp, da_haproxy_read, da_haproxy_seek,
&global_deviceatlas.atlasimgptr, &atlasimglen);
fclose(jsonp);
if (unlikely(status != DA_OK)) {
if (status != DA_OK) {
ha_alert("deviceatlas : '%s' json file is invalid.\n",
global_deviceatlas.jsonpath);
free(global_deviceatlas.atlasimgptr);
da_fini();
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
@ -201,10 +187,8 @@ static int init_deviceatlas(void)
status = da_atlas_open(&global_deviceatlas.atlas, extraprops,
global_deviceatlas.atlasimgptr, atlasimglen);
if (unlikely(status != DA_OK)) {
if (status != DA_OK) {
ha_alert("deviceatlas : data could not be compiled.\n");
free(global_deviceatlas.atlasimgptr);
da_fini();
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
@ -213,28 +197,11 @@ static int init_deviceatlas(void)
if (global_deviceatlas.cookiename == 0) {
global_deviceatlas.cookiename = strdup(DA_COOKIENAME_DEFAULT);
if (unlikely(global_deviceatlas.cookiename == NULL)) {
ha_alert("deviceatlas : out of memory.\n");
da_atlas_close(&global_deviceatlas.atlas);
free(global_deviceatlas.atlasimgptr);
da_fini();
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
}
global_deviceatlas.useragentid = da_atlas_header_evidence_id(&global_deviceatlas.atlas,
"user-agent");
{
size_t hi;
global_deviceatlas.maxhdrlen = 16;
for (hi = 0; hi < global_deviceatlas.atlas.header_evidence_count; hi++) {
size_t nl = strlen(global_deviceatlas.atlas.header_priorities[hi].name);
if (nl > global_deviceatlas.maxhdrlen)
global_deviceatlas.maxhdrlen = nl;
}
}
if ((global_deviceatlas.atlasfd = shm_open(ATLASMAPNM, O_RDWR, 0660)) != -1) {
global_deviceatlas.atlasmap = mmap(NULL, ATLASTOKSZ, PROT_READ | PROT_WRITE, MAP_SHARED, global_deviceatlas.atlasfd, 0);
if (global_deviceatlas.atlasmap == MAP_FAILED) {
@ -264,22 +231,24 @@ static void deinit_deviceatlas(void)
free(global_deviceatlas.cookiename);
da_atlas_close(&global_deviceatlas.atlas);
free(global_deviceatlas.atlasimgptr);
da_fini();
}
if (global_deviceatlas.atlasfd != -1) {
munmap(global_deviceatlas.atlasmap, ATLASTOKSZ);
close(global_deviceatlas.atlasfd);
shm_unlink(ATLASMAPNM);
}
da_fini();
}
static void da_haproxy_checkinst(void)
{
if (global_deviceatlas.atlasmap != 0) {
char *base;
base = (char *)global_deviceatlas.atlasmap;
char *base;
base = (char *)global_deviceatlas.atlasmap;
if (base[0] != 0) {
if (base[0] != 0) {
FILE *jsonp;
void *cnew;
da_status_t status;
@ -289,10 +258,6 @@ static void da_haproxy_checkinst(void)
da_property_decl_t extraprops[1] = {{NULL, 0}};
#ifdef USE_THREAD
HA_SPIN_LOCK(OTHER_LOCK, &dadwsch_lock);
if (base[0] == 0) {
HA_SPIN_UNLOCK(OTHER_LOCK, &dadwsch_lock);
return;
}
#endif
strlcpy2(atlasp, base + sizeof(char), sizeof(atlasp));
jsonp = fopen(atlasp, "r");
@ -310,20 +275,10 @@ static void da_haproxy_checkinst(void)
fclose(jsonp);
if (status == DA_OK) {
if (da_atlas_open(&inst, extraprops, cnew, atlassz) == DA_OK) {
inst.config.cache_size = global_deviceatlas.cachesize;
da_atlas_close(&global_deviceatlas.atlas);
free(global_deviceatlas.atlasimgptr);
global_deviceatlas.atlasimgptr = cnew;
global_deviceatlas.atlas = inst;
{
size_t hi;
global_deviceatlas.maxhdrlen = 16;
for (hi = 0; hi < inst.header_evidence_count; hi++) {
size_t nl = strlen(inst.header_priorities[hi].name);
if (nl > global_deviceatlas.maxhdrlen)
global_deviceatlas.maxhdrlen = nl;
}
}
base[0] = 0;
ha_notice("deviceatlas : new instance, data file date `%s`.\n",
da_getdatacreationiso8601(&global_deviceatlas.atlas));
@ -331,8 +286,6 @@ static void da_haproxy_checkinst(void)
ha_alert("deviceatlas : instance update failed.\n");
free(cnew);
}
} else {
free(cnew);
}
#ifdef USE_THREAD
HA_SPIN_UNLOCK(OTHER_LOCK, &dadwsch_lock);
@ -344,7 +297,7 @@ static void da_haproxy_checkinst(void)
static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_t *devinfo)
{
struct buffer *tmp;
da_propid_t prop;
da_propid_t prop, *pprop;
da_status_t status;
da_type_t proptype;
const char *propname;
@ -364,15 +317,13 @@ static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_
chunk_appendf(tmp, "%c", global_deviceatlas.separator);
continue;
}
if (unlikely(da_atlas_getproptype(&global_deviceatlas.atlas, prop, &proptype) != DA_OK)) {
chunk_appendf(tmp, "%c", global_deviceatlas.separator);
continue;
}
pprop = &prop;
da_atlas_getproptype(&global_deviceatlas.atlas, *pprop, &proptype);
switch (proptype) {
case DA_TYPE_BOOLEAN: {
bool val;
status = da_getpropboolean(devinfo, prop, &val);
status = da_getpropboolean(devinfo, *pprop, &val);
if (status == DA_OK) {
chunk_appendf(tmp, "%d", val);
}
@ -381,7 +332,7 @@ static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_
case DA_TYPE_INTEGER:
case DA_TYPE_NUMBER: {
long val;
status = da_getpropinteger(devinfo, prop, &val);
status = da_getpropinteger(devinfo, *pprop, &val);
if (status == DA_OK) {
chunk_appendf(tmp, "%ld", val);
}
@ -389,7 +340,7 @@ static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_
}
case DA_TYPE_STRING: {
const char *val;
status = da_getpropstring(devinfo, prop, &val);
status = da_getpropstring(devinfo, *pprop, &val);
if (status == DA_OK) {
chunk_appendf(tmp, "%s", val);
}
@ -420,26 +371,29 @@ static int da_haproxy_conv(const struct arg *args, struct sample *smp, void *pri
{
da_deviceinfo_t devinfo;
da_status_t status;
char useragentbuf[1024];
const char *useragent;
char useragentbuf[1024] = { 0 };
int i;
if (unlikely(global_deviceatlas.daset == 0) || smp->data.u.str.data == 0) {
if (global_deviceatlas.daset == 0 || smp->data.u.str.data == 0) {
return 1;
}
da_haproxy_checkinst();
i = smp->data.u.str.data > sizeof(useragentbuf) - 1 ? sizeof(useragentbuf) - 1 : smp->data.u.str.data;
memcpy(useragentbuf, smp->data.u.str.area, i);
useragentbuf[i] = 0;
i = smp->data.u.str.data > sizeof(useragentbuf) ? sizeof(useragentbuf) : smp->data.u.str.data;
memcpy(useragentbuf, smp->data.u.str.area, i - 1);
useragentbuf[i - 1] = 0;
useragent = (const char *)useragentbuf;
status = da_search(&global_deviceatlas.atlas, &devinfo,
global_deviceatlas.useragentid, useragentbuf, 0);
global_deviceatlas.useragentid, useragent, 0);
return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
}
#define DA_MAX_HEADERS 32
#define DA_MAX_HEADERS 24
static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const char *kw, void *private)
{
@ -449,10 +403,10 @@ static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const ch
struct channel *chn;
struct htx *htx;
struct htx_blk *blk;
char vbuf[DA_MAX_HEADERS][1024];
char vbuf[DA_MAX_HEADERS][1024] = {{ 0 }};
int i, nbh = 0;
if (unlikely(global_deviceatlas.daset == 0)) {
if (global_deviceatlas.daset == 0) {
return 0;
}
@ -460,17 +414,18 @@ static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const ch
chn = (smp->strm ? &smp->strm->req : NULL);
htx = smp_prefetch_htx(smp, chn, NULL, 1);
if (unlikely(!htx))
if (!htx)
return 0;
i = 0;
for (blk = htx_get_first_blk(htx); nbh < DA_MAX_HEADERS && blk; blk = htx_get_next_blk(htx, blk)) {
size_t vlen;
char *pval;
da_evidence_id_t evid;
enum htx_blk_type type;
struct ist n, v;
char hbuf[64];
char tval[1024];
char hbuf[24] = { 0 };
char tval[1024] = { 0 };
type = htx_get_blk_type(blk);
@ -483,18 +438,20 @@ static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const ch
continue;
}
if (n.len > global_deviceatlas.maxhdrlen || n.len >= sizeof(hbuf)) {
/* The HTTP headers used by the DeviceAtlas API are not longer */
if (n.len >= sizeof(hbuf)) {
continue;
}
memcpy(hbuf, n.ptr, n.len);
hbuf[n.len] = 0;
pval = v.ptr;
vlen = v.len;
evid = -1;
i = v.len > sizeof(tval) - 1 ? sizeof(tval) - 1 : v.len;
memcpy(tval, v.ptr, i);
tval[i] = 0;
pval = tval;
vlen = i;
if (strcasecmp(hbuf, "Accept-Language") == 0) {
evid = da_atlas_accept_language_evidence_id(&global_deviceatlas.atlas);
@ -512,7 +469,7 @@ static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const ch
continue;
}
vlen = pl;
vlen -= global_deviceatlas.cookienamelen - 1;
pval = p;
evid = da_atlas_clientprop_evidence_id(&global_deviceatlas.atlas);
} else {

View file

@ -141,11 +141,6 @@ enum {
DA_INITIAL_MEMORY_ESTIMATE = 1024 * 1024 * 14
};
struct header_evidence_entry {
const char *name;
da_evidence_id_t id;
};
struct da_config {
unsigned int cache_size;
unsigned int __reserved[15]; /* enough reserved keywords for future use */
@ -217,7 +212,7 @@ da_status_t da_atlas_compile(void *ctx, da_read_fn readfn, da_setpos_fn setposfn
* da_getpropid on the atlas, and if generated by the search, the ID will be consistent across
* different calls to search.
* Properties added by a search that are neither in the compiled atlas, nor in the extra_props list
* Are assigned an ID within the context that is not transferable through different search results
* Are assigned an ID within the context that is not transferrable through different search results
* within the same atlas.
* @param atlas Atlas instance
* @param extra_props properties

View file

@ -70,4 +70,4 @@ OPTIONS_OBJS += \
addons/ot/src/vars.o
endif
OT_CFLAGS := $(OT_CFLAGS) $(OT_DEFINE)
OT_CFLAGS := $(OT_CFLAGS) -Iaddons/ot/include $(OT_DEFINE)

View file

@ -47,12 +47,6 @@ 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. A
replacement filter base on OpenTelemetry is currently under development
and is expected to be ready around HAProxy 3.2. As such OpenTracing will
be deprecated in 3.3 and removed in 3.5.
The OT filter was primarily tested with the Jaeger tracer, while configurations
for both Datadog and Zipkin tracers were also set in the test directory.

View file

@ -35,11 +35,11 @@
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__); \
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_FUNC(f, ...) do { FLT_OT_DBG(1, "%s(" f ") {", __func__, ##__VA_ARGS__); dbg_indent_level += 3; } while (0)
# define FLT_OT_RETURN(a) do { dbg_indent_level -= 3; FLT_OT_DBG(1, "}"); return a; } while (0)
# define FLT_OT_RETURN_EX(a,t,f) do { 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
@ -54,7 +54,7 @@ struct flt_ot_debug {
};
extern THREAD_LOCAL int flt_ot_dbg_indent_level;
extern THREAD_LOCAL int dbg_indent_level;
extern struct flt_ot_debug flt_ot_debug;
#else

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
/***

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
/***

View file

@ -17,7 +17,7 @@
* 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"
#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 },

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
/*
@ -718,7 +718,7 @@ static void flt_ot_check_timeouts(struct stream *s, struct filter *f)
if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
FLT_OT_RETURN();
s->pending_events |= STRM_EVT_MSG;
s->pending_events |= TASK_WOKEN_MSG;
flt_ot_return_void(f, &err);

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
#define FLT_OT_GROUP_DEF(a,b,c) { a, b, c },

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
#ifdef DEBUG_OT

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
static struct pool_head *pool_head_ot_span_context __read_mostly = NULL;

View file

@ -17,12 +17,12 @@
* 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"
#include "include.h"
#ifdef DEBUG_OT
struct flt_ot_debug flt_ot_debug;
THREAD_LOCAL int flt_ot_dbg_indent_level = 0;
THREAD_LOCAL int dbg_indent_level = 0;
#endif
#ifdef OTC_DBG_MEM
@ -359,7 +359,8 @@ static int flt_ot_parse_cfg_sample(const char *file, int linenum, char **args, s
*/
static int flt_ot_parse_cfg_str(const char *file, int linenum, char **args, struct list *head, char **err)
{
int i, retval = ERR_NONE;
struct flt_ot_conf_str *str = NULL;
int i, retval = ERR_NONE;
FLT_OT_FUNC("\"%s\", %d, %p, %p, %p:%p", file, linenum, args, head, FLT_OT_DPTR_ARGS(err));
@ -367,6 +368,9 @@ static int flt_ot_parse_cfg_str(const char *file, int linenum, char **args, stru
if (flt_ot_conf_str_init(args[i], linenum, head, err) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
if (retval & ERR_CODE)
flt_ot_conf_str_free(&str);
FLT_OT_RETURN_INT(retval);
}
@ -640,7 +644,7 @@ static int flt_ot_parse_cfg_group(const char *file, int linenum, char **args, in
if (pdata->keyword == FLT_OT_PARSE_GROUP_ID) {
flt_ot_current_group = flt_ot_conf_group_init(args[1], linenum, &(flt_ot_current_config->groups), &err);
if (flt_ot_current_group == NULL)
if (flt_ot_current_config == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
else if (pdata->keyword == FLT_OT_PARSE_GROUP_SCOPES) {
@ -1070,9 +1074,8 @@ static int flt_ot_post_parse_cfg_scope(void)
*/
static int flt_ot_parse_cfg(struct flt_ot_conf *conf, const char *flt_name, char **err)
{
struct list backup_sections;
struct cfgfile cfg_file = {0};
int retval = ERR_ABORT | ERR_ALERT;
struct list backup_sections;
int retval = ERR_ABORT | ERR_ALERT;
FLT_OT_FUNC("%p, \"%s\", %p:%p", conf, flt_name, FLT_OT_DPTR_ARGS(err));
@ -1091,16 +1094,8 @@ static int flt_ot_parse_cfg(struct flt_ot_conf *conf, const char *flt_name, char
/* Do nothing. */;
else if (access(conf->cfg_file, R_OK) == -1)
FLT_OT_PARSE_ERR(err, "'%s' : %s", conf->cfg_file, strerror(errno));
else {
cfg_file.filename = conf->cfg_file;
cfg_file.size = load_cfg_in_mem(cfg_file.filename, &cfg_file.content);
if (cfg_file.size < 0) {
ha_free(&cfg_file.content);
FLT_OT_RETURN_INT(retval);
}
retval = parse_cfg(&cfg_file);
ha_free(&cfg_file.content);
}
else
retval = readcfgfile(conf->cfg_file);
/* Unregister OT sections and restore previous sections. */
cfg_unregister_sections();

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
/***

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
static struct pool_head *pool_head_ot_scope_span __read_mostly = NULL;
@ -113,7 +113,7 @@ struct flt_ot_runtime_context *flt_ot_runtime_context_init(struct stream *s, str
LIST_INIT(&(retptr->contexts));
uuid = b_make(retptr->uuid, sizeof(retptr->uuid), 0, 0);
ha_generate_uuid_v4(&uuid);
ha_generate_uuid(&uuid);
#ifdef USE_OT_VARS
/*

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
#ifdef DEBUG_OT
@ -41,7 +41,7 @@ void flt_ot_args_dump(char **args)
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]);
(void)fprintf(stderr, FLT_OT_DBG_FMT("%.*sargs[%d]: { '%s' "), dbg_indent_level, FLT_OT_DBG_INDENT, argc, args[0]);
for (i = 1; i < argc; i++)
(void)fprintf(stderr, "'%s' ", args[i]);

View file

@ -17,7 +17,7 @@
* 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"
#include "include.h"
#ifdef DEBUG_OT
@ -39,21 +39,14 @@
*/
static void flt_ot_vars_scope_dump(struct vars *vars, const char *scope)
{
int i;
const struct var *var;
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)));
}
}
list_for_each_entry(var, &(vars->head), l)
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);
}

View file

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

View file

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

View file

@ -1,80 +0,0 @@
# USE_OTEL : enable the OpenTelemetry filter
# OTEL_DEBUG : compile the OpenTelemetry filter in debug mode
# OTEL_INC : force the include path to libopentelemetry-c-wrapper
# OTEL_LIB : force the lib path to libopentelemetry-c-wrapper
# OTEL_RUNPATH : add libopentelemetry-c-wrapper RUNPATH to haproxy executable
# OTEL_USE_VARS : allows the use of variables for the OpenTelemetry context
OTEL_DEFINE =
OTEL_CFLAGS =
OTEL_LDFLAGS =
OTEL_DEBUG_EXT =
OTEL_PKGSTAT =
OTELC_WRAPPER = opentelemetry-c-wrapper
ifneq ($(OTEL_DEBUG:0=),)
OTEL_DEBUG_EXT = _dbg
OTEL_DEFINE = -DDEBUG_OTEL
endif
ifeq ($(OTEL_INC),)
OTEL_PKGSTAT = $(shell pkg-config --exists $(OTELC_WRAPPER)$(OTEL_DEBUG_EXT); echo $$?)
OTEL_CFLAGS = $(shell pkg-config --silence-errors --cflags $(OTELC_WRAPPER)$(OTEL_DEBUG_EXT))
else
ifneq ($(wildcard $(OTEL_INC)/$(OTELC_WRAPPER)/.*),)
OTEL_CFLAGS = -I$(OTEL_INC) $(if $(OTEL_DEBUG),-DOTELC_DBG_MEM)
endif
endif
ifeq ($(OTEL_PKGSTAT),)
ifeq ($(OTEL_CFLAGS),)
$(error OpenTelemetry C wrapper : can't find headers)
endif
else
ifneq ($(OTEL_PKGSTAT),0)
$(error OpenTelemetry C wrapper : can't find package)
endif
endif
ifeq ($(OTEL_LIB),)
OTEL_LDFLAGS = $(shell pkg-config --silence-errors --libs $(OTELC_WRAPPER)$(OTEL_DEBUG_EXT))
else
ifneq ($(wildcard $(OTEL_LIB)/lib$(OTELC_WRAPPER).*),)
OTEL_LDFLAGS = -L$(OTEL_LIB) -l$(OTELC_WRAPPER)$(OTEL_DEBUG_EXT)
ifneq ($(OTEL_RUNPATH),)
OTEL_LDFLAGS += -Wl,--rpath,$(OTEL_LIB)
endif
endif
endif
ifeq ($(OTEL_LDFLAGS),)
$(error OpenTelemetry C wrapper : can't find library)
endif
OPTIONS_OBJS += \
addons/otel/src/cli.o \
addons/otel/src/conf.o \
addons/otel/src/event.o \
addons/otel/src/filter.o \
addons/otel/src/group.o \
addons/otel/src/http.o \
addons/otel/src/otelc.o \
addons/otel/src/parser.o \
addons/otel/src/pool.o \
addons/otel/src/scope.o \
addons/otel/src/util.o
ifneq ($(OTEL_USE_VARS:0=),)
OTEL_DEFINE += -DUSE_OTEL_VARS
OPTIONS_OBJS += addons/otel/src/vars.o
# Auto-detect whether struct var has a 'name' member. When present,
# prefix-based variable scanning can be used instead of the tracking
# buffer approach.
OTEL_VAR_HAS_NAME := $(shell awk '/^struct var \{/,/^\}/' include/haproxy/vars-t.h 2>/dev/null | grep -q '[*]name;' && echo 1)
ifneq ($(OTEL_VAR_HAS_NAME),)
OTEL_DEFINE += -DUSE_OTEL_VARS_NAME
endif
endif
OTEL_CFLAGS := $(OTEL_CFLAGS) -Iaddons/otel/include $(OTEL_DEFINE)

File diff suppressed because it is too large Load diff

View file

@ -1,456 +0,0 @@
OpenTelemetry Filter Configuration Structures
==============================================================================
1 Overview
------------------------------------------------------------------------------
The OpenTelemetry filter configuration is a tree of C structures that mirrors
the hierarchical layout of the filter's configuration file. Each structure type
carries a common header macro, and its allocation and deallocation are performed
by macro-generated init/free function pairs defined in conf_funcs.h.
The root of the tree is flt_otel_conf, which owns the instrumentation settings,
groups, and scopes. Scopes contain the actual tracing and metrics definitions:
contexts, spans, instruments, and their sample expressions.
Source files:
include/conf.h Structure definitions and debug macros.
include/conf_funcs.h Init/free macro templates and declarations.
src/conf.c Init/free implementations for all types.
2 Common Macros
------------------------------------------------------------------------------
Two macros provide the building blocks embedded in every configuration
structure.
2.1 FLT_OTEL_CONF_STR(p)
Expands to an anonymous struct containing a string pointer and its cached
length:
struct {
char *p;
size_t p_len;
};
Used for auxiliary string fields that do not need list linkage (e.g. ref_id and
ctx_id in flt_otel_conf_span).
2.2 FLT_OTEL_CONF_HDR(p)
Expands to an anonymous struct that extends FLT_OTEL_CONF_STR with a
configuration file line number and an intrusive list node:
struct {
char *p;
size_t p_len;
int cfg_line;
struct list list;
};
Every configuration structure embeds FLT_OTEL_CONF_HDR as its first member.
The <p> parameter names the identifier field (e.g. "id", "key", "str", "span",
"fmt_expr"). The list node chains the structure into its parent's list.
The cfg_line records the source line for error reporting.
3 Structure Hierarchy
------------------------------------------------------------------------------
The complete ownership tree, from root to leaves:
flt_otel_conf
+-- flt_otel_conf_instr (one, via pointer)
| +-- flt_otel_conf_ph (ph_groups list)
| +-- flt_otel_conf_ph (ph_scopes list)
| +-- struct acl (acls list, HAProxy-owned type)
| +-- struct logger (proxy_log.loggers, HAProxy type)
+-- flt_otel_conf_group (groups list)
| +-- flt_otel_conf_ph (ph_scopes list)
+-- flt_otel_conf_scope (scopes list)
+-- flt_otel_conf_context (contexts list)
+-- flt_otel_conf_span (spans list)
| +-- flt_otel_conf_link (links list)
| +-- flt_otel_conf_sample (attributes list)
| | +-- flt_otel_conf_sample_expr (exprs list)
| +-- flt_otel_conf_sample (events list)
| | +-- flt_otel_conf_sample_expr (exprs list)
| +-- flt_otel_conf_sample (baggages list)
| | +-- flt_otel_conf_sample_expr (exprs list)
| +-- flt_otel_conf_sample (statuses list)
| +-- flt_otel_conf_sample_expr (exprs list)
+-- flt_otel_conf_str (spans_to_finish list)
+-- flt_otel_conf_instrument (instruments list)
| +-- flt_otel_conf_sample (samples list)
| +-- flt_otel_conf_sample_expr (exprs list)
+-- flt_otel_conf_log_record (log_records list)
+-- flt_otel_conf_sample (attributes list)
| +-- flt_otel_conf_sample_expr (exprs list)
+-- flt_otel_conf_sample (samples list)
+-- flt_otel_conf_sample_expr (exprs list)
All child lists use HAProxy's intrusive doubly-linked list (struct list)
threaded through the FLT_OTEL_CONF_HDR embedded in each child structure.
3.1 Placeholder Structures
The flt_otel_conf_ph structure serves as an indirection node. During parsing,
placeholder entries record names of groups and scopes. At check time
(flt_otel_check), these names are resolved to pointers to the actual
flt_otel_conf_group or flt_otel_conf_scope structures via the ptr field.
Two type aliases exist for clarity:
#define flt_otel_conf_ph_group flt_otel_conf_ph
#define flt_otel_conf_ph_scope flt_otel_conf_ph
Corresponding free aliases ensure the FLT_OTEL_LIST_DESTROY macro can locate
the correct free function:
#define flt_otel_conf_ph_group_free flt_otel_conf_ph_free
#define flt_otel_conf_ph_scope_free flt_otel_conf_ph_free
4 Structure Definitions
------------------------------------------------------------------------------
4.1 flt_otel_conf (root)
proxy Proxy owning the filter.
id The OpenTelemetry filter id.
cfg_file The OpenTelemetry filter configuration file name.
instr The OpenTelemetry instrumentation settings (pointer).
groups List of all available groups.
scopes List of all available scopes.
cnt Various counters related to filter operation.
smp_args Deferred sample fetch arguments to resolve at check time.
This structure does not use FLT_OTEL_CONF_HDR because it is not part of any
list -- it is the unique root, owned by the filter instance.
4.2 flt_otel_conf_instr (instrumentation)
FLT_OTEL_CONF_HDR(id) The OpenTelemetry instrumentation name.
config The OpenTelemetry configuration file name.
tracer The OpenTelemetry tracer handle.
meter The OpenTelemetry meter handle.
logger The OpenTelemetry logger handle.
rate_limit Rate limit as uint32 ([0..2^32-1] maps [0..100]%).
flag_harderr Hard-error mode flag.
flag_disabled Disabled flag.
logging Logging mode (0, 1, or 3).
proxy_log The log server list (HAProxy proxy structure).
analyzers Defined channel analyzers bitmask.
idle_timeout Minimum idle timeout across scopes (ms, 0 = off).
acls ACLs declared on this tracer.
ph_groups List of all used groups (placeholders).
ph_scopes List of all used scopes (placeholders).
Exactly one instrumentation block is allowed per filter instance. The parser
stores a pointer to it in flt_otel_conf.instr.
4.3 flt_otel_conf_group
FLT_OTEL_CONF_HDR(id) The group name.
flag_used The indication that the group is being used.
ph_scopes List of all used scopes (placeholders).
Groups bundle scopes for use with the "otel-group" HAProxy action.
4.4 flt_otel_conf_scope
FLT_OTEL_CONF_HDR(id) The scope name.
flag_used The indication that the scope is being used.
event FLT_OTEL_EVENT_* identifier.
idle_timeout Idle timeout interval in milliseconds (0 = off).
acls ACLs declared on this scope.
cond ACL condition to meet.
contexts Declared contexts.
spans Declared spans.
spans_to_finish The list of spans scheduled for finishing.
instruments The list of metric instruments.
log_records The list of log records.
Each scope binds to a single HAProxy analyzer event (or none, if used only
through groups).
4.5 flt_otel_conf_span
FLT_OTEL_CONF_HDR(id) The name of the span.
FLT_OTEL_CONF_STR(ref_id) The reference name, if used.
FLT_OTEL_CONF_STR(ctx_id) The span context name, if used.
ctx_flags The type of storage used for the span context.
flag_root Whether this is a root span.
links The set of linked span names.
attributes The set of key:value attributes.
events The set of events with key-value attributes.
baggages The set of key:value baggage items.
statuses Span status code and description.
The ref_id and ctx_id fields use FLT_OTEL_CONF_STR because they are simple name
strings without list linkage.
4.6 flt_otel_conf_instrument
FLT_OTEL_CONF_HDR(id) The name of the instrument.
idx Meter instrument index: UNSET (-1) before creation,
PENDING (-2) while another thread is creating, or >= 0
for the actual meter index.
type Instrument type (or UPDATE).
aggr_type Aggregation type for the view (create only).
description Instrument description (create only).
unit Instrument unit (create only).
samples Sample expressions for the value.
bounds Histogram bucket boundaries (create only).
bounds_num Number of histogram bucket boundaries.
attributes Instrument attributes (update only, flt_otel_conf_sample).
ref Resolved create-form instrument (update only).
Instruments come in two forms: create-form (defines a new metric with type,
description, unit, and optional histogram bounds) and update-form (references
an existing instrument via the ref pointer). Update-form attributes are stored
as flt_otel_conf_sample entries and evaluated at runtime.
4.7 flt_otel_conf_log_record
FLT_OTEL_CONF_HDR(id) Required by macro; member <id> is not used directly.
severity The severity level.
event_id Optional event identifier.
event_name Optional event name.
span Optional span reference.
attributes Log record attributes (flt_otel_conf_sample list).
samples Sample expressions for the body.
Log records are emitted via the OTel logger at the configured severity. The
optional span reference associates the log record with an open span at runtime.
Attributes are stored as flt_otel_conf_sample entries added via the 'attr'
keyword, which can be repeated. Attribute values are HAProxy sample expressions
evaluated at runtime.
4.8 flt_otel_conf_context
FLT_OTEL_CONF_HDR(id) The name of the context.
flags Storage type from which the span context is extracted.
4.9 flt_otel_conf_sample
FLT_OTEL_CONF_HDR(key) The list containing sample names.
fmt_string Combined sample-expression arguments string.
extra Optional supplementary data.
exprs Used to chain sample expressions.
num_exprs Number of defined expressions.
lf_expr The log-format expression.
lf_used Whether lf_expr is used instead of exprs.
The extra field carries type-specific data: event name strings (OTELC_VALUE_DATA)
for span events, status code integers (OTELC_VALUE_INT32) for span statuses.
When the sample value argument contains the "%[" sequence, the parser treats
it as a log-format string: the lf_used flag is set and the compiled result is
stored in lf_expr, while the exprs list remains empty. At runtime, if lf_used
is true, the log-format expression is evaluated via build_logline() instead of
the sample expression list.
4.10 flt_otel_conf_sample_expr
FLT_OTEL_CONF_HDR(fmt_expr) The original expression format string.
expr The sample expression (struct sample_expr).
4.11 Simple Types
flt_otel_conf_hdr Generic header; used for simple named entries.
flt_otel_conf_str String holder (identical to conf_hdr in layout, but the
HDR field is named "str" instead of "id"); used for
spans_to_finish.
flt_otel_conf_link Span link reference; HDR field named "span".
flt_otel_conf_ph Placeholder; carries a ptr field resolved at check time.
5 Initialization
------------------------------------------------------------------------------
5.1 Macro-Generated Init Functions
The FLT_OTEL_CONF_FUNC_INIT macro (conf_funcs.h) generates a function with the
following signature for each configuration type:
struct flt_otel_conf_<type> *flt_otel_conf_<type>_init(const char *id, int line, struct list *head, char **err);
The generated function performs these steps:
1. Validates that <id> is non-NULL and non-empty.
2. Checks the identifier length against FLT_OTEL_ID_MAXLEN (64).
3. If <head> is non-NULL, iterates the list to reject duplicate identifiers
(strcmp match).
4. Allocates the structure with OTELC_CALLOC (zeroed memory).
5. Records the configuration line number in cfg_line.
6. Duplicates the identifier string with OTELC_STRDUP.
7. If <head> is non-NULL, appends the structure to the list via LIST_APPEND.
8. Executes any custom initialization body provided as the third macro
argument.
If any step fails, the function sets an error message via FLT_OTEL_ERR and
returns NULL.
5.2 Custom Initialization Bodies
Several structure types require additional setup beyond what the macro template
provides. The custom init body runs after the base allocation and list
insertion succeed:
conf_sample:
LIST_INIT for exprs. Calls lf_expr_init for lf_expr.
conf_span:
LIST_INIT for links, attributes, events, baggages, statuses.
conf_scope:
LIST_INIT for acls, contexts, spans, spans_to_finish, instruments,
log_records.
conf_group:
LIST_INIT for ph_scopes.
conf_instrument:
Sets idx and type to OTELC_METRIC_INSTRUMENT_UNSET, aggr_type to
OTELC_METRIC_AGGREGATION_UNSET. LIST_INIT for samples.
conf_log_record:
LIST_INIT for samples.
conf_instr:
Sets rate_limit to FLT_OTEL_FLOAT_U32(100.0) (100%). Calls init_new_proxy
for proxy_log. LIST_INIT for acls, ph_groups, ph_scopes.
Types with no custom body (hdr, str, link, ph, sample_expr, context) rely
entirely on the zeroed OTELC_CALLOC allocation.
5.3 Extended Sample Initialization
The flt_otel_conf_sample_init_ex function (conf.c) provides a higher-level
initialization for sample structures:
1. Verifies sufficient arguments in the args[] array.
2. Calls flt_otel_conf_sample_init with the sample key.
3. Copies extra data (event name string or status code integer).
4. Concatenates remaining arguments into the fmt_string via
flt_otel_args_concat.
5. Counts the number of sample expressions.
This function is used by the parser for span attributes, events, baggages,
statuses, and instrument samples.
5.4 Top-Level Initialization
The flt_otel_conf_init function (conf.c) is hand-written rather than
macro-generated because the root structure does not follow the standard header
pattern:
1. Allocates flt_otel_conf with OTELC_CALLOC.
2. Stores the proxy reference.
3. Initializes the groups and scopes lists.
6 Deallocation
------------------------------------------------------------------------------
6.1 Macro-Generated Free Functions
The FLT_OTEL_CONF_FUNC_FREE macro (conf_funcs.h) generates a function with the
following signature:
void flt_otel_conf_<type>_free(struct flt_otel_conf_<type> **ptr);
The generated function performs these steps:
1. Checks that both <ptr> and <*ptr> are non-NULL.
2. Executes any custom cleanup body provided as the third macro argument.
3. Frees the identifier string with OTELC_SFREE.
4. Removes the structure from its list with FLT_OTEL_LIST_DEL.
5. Frees the structure with OTELC_SFREE_CLEAR and sets <*ptr> to NULL.
6.2 Custom Cleanup Bodies
Custom cleanup runs before the base teardown, allowing child structures to be
freed while the parent is still valid:
conf_sample:
Frees fmt_string. If extra is OTELC_VALUE_DATA, frees the data pointer.
Destroys the exprs list (sample_expr entries). Deinitializes lf_expr via
lf_expr_deinit.
conf_sample_expr:
Releases the HAProxy sample expression via release_sample_expr.
conf_span:
Frees ref_id and ctx_id strings.
Destroys links, attributes, events, baggages, and statuses lists.
conf_instrument:
Frees description, unit, and bounds. Destroys the samples list.
Destroys the attr key-value array via otelc_kv_destroy.
conf_log_record:
Frees event_name and span strings. Destroys the attr key-value array via
otelc_kv_destroy. Destroys the samples list.
conf_scope:
Prunes and frees each ACL entry. Frees the ACL condition via free_acl_cond.
Destroys contexts, spans, spans_to_finish, instruments, and log_records
lists.
conf_group:
Destroys the ph_scopes list.
conf_instr:
Frees the config string. Prunes and frees each ACL entry. Frees each
logger entry from proxy_log.loggers. Destroys the ph_groups and ph_scopes
lists.
Types with no custom cleanup (hdr, str, link, ph, context) only run the base
teardown: free the identifier, unlink, free the structure.
6.3 List Destruction
The FLT_OTEL_LIST_DESTROY(type, head) macro (defined in define.h) iterates a
list and calls flt_otel_conf_<type>_free for each entry. This macro drives the
recursive teardown from parent to leaf.
6.4 Top-Level Deallocation
The flt_otel_conf_free function (conf.c) is hand-written:
1. Frees the id and cfg_file strings.
2. Calls flt_otel_conf_instr_free for the instrumentation.
3. Destroys the groups list (which recursively frees placeholders).
4. Destroys the scopes list (which recursively frees contexts, spans,
instruments, log records, and all their children).
5. Frees the root structure and sets the pointer to NULL.
7 Summary of Init/Free Pairs
------------------------------------------------------------------------------
The following table lists all configuration types and their init/free function
pairs. Types marked "macro" are generated by the FLT_OTEL_CONF_FUNC_(INIT|FREE)
macros. The HDR field column shows which member name is used for the common
header.
Type HDR field Source Custom init body
--------------- --------- ------ -------------------------
conf (none) manual groups, scopes
conf_hdr id macro (none)
conf_str str macro (none)
conf_link span macro (none)
conf_ph id macro (none)
conf_sample_expr fmt_expr macro (none)
conf_sample key macro exprs, lf_expr
conf_sample (ex) key manual extra, fmt_string, exprs
conf_context id macro (none)
conf_span id macro 5 sub-lists
conf_instrument id macro idx, type, samples
conf_log_record id macro samples
conf_scope id macro 6 sub-lists
conf_group id macro ph_scopes
conf_instr id macro rate_limit, proxy_log, acls, ph_groups, ph_scopes

View file

@ -1,951 +0,0 @@
-----------------------------------------
HAProxy OTel filter configuration guide
Version 1.0
( Last update: 2026-03-18 )
-----------------------------------------
Author : Miroslav Zagorac
Contact : mzagorac at haproxy dot com
SUMMARY
--------
1. Overview
2. HAProxy filter declaration
3. OTel configuration file structure
3.1. OTel scope (top-level)
3.2. "otel-instrumentation" section
3.3. "otel-scope" section
3.4. "otel-group" section
4. YAML configuration file
4.1. Exporters
4.2. Samplers
4.3. Processors
4.4. Readers
4.5. Providers
4.6. Signals
5. HAProxy rule integration
6. Complete examples
6.1. Standalone example (sa)
6.2. Frontend / backend example (fe/be)
6.3. Context propagation example (ctx)
6.4. Comparison example (cmp)
6.5. Empty / minimal example (empty)
1. Overview
------------
The OTel filter configuration consists of two files:
1) An OTel configuration file (.cfg) that defines the tracing model: scopes,
groups, spans, attributes, events, instrumentation and log-records.
2) A YAML configuration file (.yml) that configures the OpenTelemetry SDK
pipeline: exporters, samplers, processors, readers, providers and signal
routing.
The OTel configuration file is referenced from the HAProxy configuration using
the 'filter opentelemetry' directive. The YAML file is in turn referenced from
the OTel configuration file using the 'config' keyword inside the
"otel-instrumentation" section.
2. HAProxy filter declaration
------------------------------
The OTel filter requires the 'insecure-fork-wanted' keyword in the HAProxy
'global' section. This is necessary because the OpenTelemetry C++ SDK creates
background threads for data export and batch processing. HAProxy will refuse
to load the configuration if this keyword is missing.
global
insecure-fork-wanted
...
The filter is activated by adding a filter directive in the HAProxy
configuration, in a proxy section (frontend / listen / backend):
frontend my-frontend
...
filter opentelemetry [id <id>] config <otel-cfg-file>
...
If no filter id is specified, 'otel-filter' is used as default. The 'config'
parameter is mandatory and specifies the path to the OTel configuration file.
Example (from test/sa/haproxy.cfg):
frontend otel-test-sa-frontend
bind *:10080
default_backend servers-backend
acl acl-http-status-ok status 100:399
filter opentelemetry id otel-test-sa config sa/otel.cfg
http-response otel-group otel-test-sa http_response_group if acl-http-status-ok
http-after-response otel-group otel-test-sa http_after_response_group if !acl-http-status-ok
backend servers-backend
server server-1 127.0.0.1:8000
3. OTel configuration file structure
--------------------------------------
The OTel configuration file uses a simple section-based format. It contains
three types of sections: one "otel-instrumentation" section (mandatory), zero
or more "otel-scope" sections, and zero or more "otel-group" sections.
3.1. OTel scope (top-level)
-----------------------------
The file is organized into top-level OTel scopes, each identified by a filter
id enclosed in square brackets. The filter id must match the id specified in
the HAProxy 'filter opentelemetry' directive.
[<filter-id>]
otel-instrumentation <name>
...
otel-group <name>
...
otel-scope <name>
...
Multiple OTel scopes (for different filter instances) can coexist in the same
file:
[my-first-filter]
otel-instrumentation instr1
...
[my-second-filter]
otel-instrumentation instr2
...
3.2. "otel-instrumentation" section
-------------------------------------
Exactly one "otel-instrumentation" section must be defined per OTel scope.
It configures the global behavior of the filter and declares which groups
and scopes are active.
Syntax:
otel-instrumentation <name>
Keywords (mandatory):
config <file>
Path to the YAML configuration file for the OpenTelemetry SDK.
Keywords (optional):
acl <aclname> <criterion> [flags] [operator] <value> ...
Declare an ACL. See section 7 of the HAProxy Configuration Manual.
debug-level <value>
Set the debug level bitmask (e.g. 0x77f). Only effective when compiled
with OTEL_DEBUG=1.
groups <name> ...
Declare one or more "otel-group" sections used by this instrumentation.
Can be repeated on multiple lines.
log global
log <addr> [len <len>] [format <fmt>] <facility> [<level> [<minlvl>]]
no log
Enable per-instance logging.
option disabled / no option disabled
Disable or enable the filter. Default: enabled.
option dontlog-normal / no option dontlog-normal
Suppress logging for normal (successful) operations. Default: disabled.
option hard-errors / no option hard-errors
Stop all filter processing in a stream after the first error. Default:
disabled (errors are non-fatal).
rate-limit <value>
Percentage of streams for which the filter is activated. Floating-point
value from 0.0 to 100.0. Default: 100.0.
scopes <name> ...
Declare one or more "otel-scope" sections used by this instrumentation.
Can be repeated on multiple lines.
Example (from test/sa/otel.cfg):
[otel-test-sa]
otel-instrumentation otel-test-instrumentation
debug-level 0x77f
log localhost:514 local7 debug
config sa/otel.yml
option dontlog-normal
option hard-errors
no option disabled
rate-limit 100.0
groups http_response_group
groups http_after_response_group
scopes on_stream_start
scopes on_stream_stop
scopes client_session_start
scopes frontend_tcp_request
...
scopes server_session_end
3.3. "otel-scope" section
---------------------------
An "otel-scope" section defines the actions that take place when a particular
event fires or when a group is triggered.
Syntax:
otel-scope <name>
Supported keywords:
span <name> [parent <ref>] [link <ref>] [root]
Create a new span or reference an already opened one.
- 'root' marks this span as the trace root (only one per trace).
- 'parent <ref>' sets the parent to an existing span or extracted context
name.
- 'link <ref>' adds an inline link to another span or context. Multiple
inline links can be specified within the argument limit.
- If no reference is given, the span becomes a root span.
A span declaration opens a "sub-context" within the scope: the keywords
'link', 'attribute', 'event', 'baggage', 'status' and 'inject' that follow
apply to that span until the next 'span' keyword or the end of the scope.
Examples:
span "HAProxy session" root
span "Client session" parent "HAProxy session"
span "HTTP request" parent "TCP request" link "HAProxy session"
span "Client session" parent "otel_ctx_1"
attribute <key> <sample> ...
Set an attribute on the currently active span. A single sample preserves
its native type; multiple samples are concatenated as a string.
Examples:
attribute "http.method" method
attribute "http.url" url
attribute "http.version" str("HTTP/") req.ver
event <name> <key> <sample> ...
Add a span event (timestamped annotation) to the currently active span.
The data type is always string.
Examples:
event "event_ip" "src" src str(":") src_port
event "event_be" "be" be_id str(" ") be_name
baggage <key> <sample> ...
Set baggage on the currently active span. Baggage propagates to all child
spans. The data type is always string.
Example:
baggage "haproxy_id" var(sess.otel.uuid)
status <code> [<sample> ...]
Set the span status. Valid codes: ignore (default), unset, ok, error.
An optional description follows the code.
Examples:
status "ok"
status "error" str("http.status_code: ") status
link <span> ...
Add non-hierarchical links to the currently active span. Multiple span
names can be specified. Use this keyword for multiple links (the inline
'link' in 'span' is limited to one).
Example:
link "HAProxy session" "Client session"
inject <name-prefix> [use-vars] [use-headers]
Inject span context into an HTTP header carrier and/or HAProxy variables.
The prefix names the context; the special prefix '-' generates the name
automatically. Default storage: use-headers. The 'use-vars' option
requires OTEL_USE_VARS=1 at compile time.
Example:
span "HAProxy session" root
inject "otel_ctx_1" use-headers use-vars
extract <name-prefix> [use-vars | use-headers]
Extract a previously injected span context from an HTTP header or HAProxy
variables. The extracted context can then be used as a parent reference
in 'span ... parent <name-prefix>'.
Example:
extract "otel_ctx_1" use-vars
span "Client session" parent "otel_ctx_1"
finish <name> ...
Close one or more spans or span contexts. Special names:
'*' - finish all open spans
'*req*' - finish all request-channel spans
'*res*' - finish all response-channel spans
Multiple names can be given on one line. A quoted context name after a
span name finishes the associated context as well.
Examples:
finish "Frontend TCP request"
finish "Client session" "otel_ctx_2"
finish *
instrument <type> <name> [aggr <aggregation>] [desc <description>] [unit <unit>] value <sample> [bounds <bounds>]
instrument update <name> [attr <key> <sample> ...]
Create or update a metric instrument.
Supported types:
cnt_int - counter (uint64)
hist_int - histogram (uint64)
udcnt_int - up-down counter (int64)
gauge_int - gauge (int64)
Supported aggregation types:
drop - measurements are discarded
histogram - explicit bucket histogram
last_value - last recorded value
sum - sum of recorded values
default - SDK default for the instrument type
exp_histogram - base-2 exponential histogram
An aggregation type can be specified using the 'aggr' keyword. When
specified, a metrics view is registered with the given aggregation
strategy. If omitted, the SDK default is used.
For histogram instruments (hist_int), optional bucket boundaries can be
specified using the 'bounds' keyword followed by a double-quoted string
of space-separated numbers (order does not matter; values are sorted
internally). When bounds are specified without an explicit aggregation
type, histogram aggregation is used automatically.
Observable (asynchronous) and double-precision types are not supported.
Observable instrument callbacks are invoked by the OTel SDK from an
external background thread; HAProxy sample fetches rely on internal
per-thread-group state and return incorrect results from a non-HAProxy
thread. Double-precision types are not supported because HAProxy sample
fetches do not return double values.
Examples:
instrument cnt_int "name_cnt_int" desc "Integer Counter" value int(1),add(2) unit "unit"
instrument hist_int "name_hist" aggr exp_histogram desc "Latency" value lat_ns_tot unit "ns"
instrument hist_int "name_hist2" desc "Latency" value lat_ns_tot unit "ns" bounds "100 1000 10000"
instrument update "name_cnt_int" attr "attr_1_key" str("attr_1_value")
log-record <severity> [id <integer>] [event <name>] [span <span-name>] [attr <key> <sample>] ... <sample> ...
Emit an OpenTelemetry log record. The first argument is a required
severity level. Optional keywords follow in any order:
id <integer> - numeric event identifier
event <name> - event name string
span <span-name> - associate the log record with an open span
attr <key> <sample> - add an attribute evaluated at runtime (repeatable)
The 'attr' keyword takes an attribute name and a single HAProxy sample
expression. The expression is evaluated at runtime, following the same
rules as span attributes: a bare sample fetch (e.g. src) or a log-format
string (e.g. "%[src]:%[src_port]").
The remaining arguments at the end are sample fetch expressions that form
the log record body. A single sample preserves its native type; multiple
samples are concatenated as a string.
Supported severity levels follow the OpenTelemetry specification:
trace, trace2, trace3, trace4
debug, debug2, debug3, debug4
info, info2, info3, info4
warn, warn2, warn3, warn4
error, error2, error3, error4
fatal, fatal2, fatal3, fatal4
The log record is only emitted if the logger is enabled for the configured
severity (controlled by the 'min_severity' option in the YAML logs signal
configuration). If a 'span' reference is given but the named span is not
found at runtime, the log record is emitted without span correlation.
Examples:
log-record info str("heartbeat")
log-record info id 1001 event "http-request" span "Frontend HTTP request" attr "http.method" method method url
log-record trace id 1000 event "session-start" span "Client session" attr "src_ip" src attr "src_port" src_port src str(":") src_port
log-record warn event "server-unavailable" str("503 Service Unavailable")
log-record info event "session-stop" str("stream stopped")
acl <aclname> <criterion> [flags] [operator] <value> ...
Declare an ACL local to this scope.
Example:
acl acl-test-src-ip src 127.0.0.1
otel-event <name> [{ if | unless } <condition>]
Bind this scope to a filter event, optionally with an ACL-based condition.
Supported events (stream lifecycle):
on-stream-start
on-stream-stop
on-idle-timeout
on-backend-set
Supported events (request channel):
on-client-session-start
on-frontend-tcp-request
on-http-wait-request
on-http-body-request
on-frontend-http-request
on-switching-rules-request
on-backend-tcp-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-http-headers-request
on-http-end-request
on-client-session-end
on-server-unavailable
Supported events (response channel):
on-server-session-start
on-tcp-response
on-http-wait-response
on-process-store-rules-response
on-http-response
on-http-headers-response
on-http-end-response
on-http-reply
on-server-session-end
The on-stream-start event fires from the stream_start filter callback,
before any channel processing begins. The on-stream-stop event fires from
the stream_stop callback, after all channel processing ends. No channel
is available at that point, so context injection/extraction via HTTP
headers cannot be used in scopes bound to these events.
The on-idle-timeout event fires periodically when the stream has no data
transfer activity. It requires the 'idle-timeout' keyword to set the
interval. Scopes bound to this event can create heartbeat spans, record
idle-time metrics, and emit idle-time log records.
The on-backend-set event fires when a backend is assigned to the stream.
It is not called if the frontend and backend are the same.
The on-http-headers-request and on-http-headers-response events fire after
all HTTP headers have been parsed and analyzed.
The on-http-end-request and on-http-end-response events fire when all HTTP
data has been processed and forwarded.
The on-http-reply event fires when HAProxy generates an internal reply
(error page, deny response, redirect).
Examples:
otel-event on-stream-start if acl-test-src-ip
otel-event on-stream-stop
otel-event on-client-session-start
otel-event on-client-session-start if acl-test-src-ip
otel-event on-http-response if !acl-http-status-ok
otel-event on-idle-timeout
idle-timeout <time>
Set the idle timeout interval for a scope bound to the 'on-idle-timeout'
event. The timer fires periodically at the given interval when the stream
has no data transfer activity. This keyword is mandatory for scopes using
the 'on-idle-timeout' event and cannot be used with any other event.
The <time> argument accepts the standard HAProxy time format: a number
followed by a unit suffix (ms, s, m, h, d). A value of zero is not
permitted.
Example:
scopes on_idle_timeout
..
otel-scope on_idle_timeout
idle-timeout 5s
span "heartbeat" root
attribute "idle.elapsed" str("idle-check")
instrument cnt_int "idle.count" value int(1)
log-record info str("heartbeat")
otel-event on-idle-timeout
3.4. "otel-group" section
---------------------------
An "otel-group" section defines a named collection of scopes that can be
triggered from HAProxy TCP/HTTP rules rather than from filter events.
Syntax:
otel-group <name>
Keywords:
scopes <name> ...
List the "otel-scope" sections that belong to this group. Multiple names
can be given on one line. Scopes that are used only in groups do not need
to define an 'otel-event'.
Example (from test/sa/otel.cfg):
otel-group http_response_group
scopes http_response_1
scopes http_response_2
otel-scope http_response_1
span "HTTP response"
event "event_content" "hdr.content" res.hdr("content-type") str("; length: ") res.hdr("content-length") str(" bytes")
otel-scope http_response_2
span "HTTP response"
event "event_date" "hdr.date" res.hdr("date") str(" / ") res.hdr("last-modified")
4. YAML configuration file
----------------------------
The YAML configuration file defines the OpenTelemetry SDK pipeline. It is
referenced by the 'config' keyword in the "otel-instrumentation" section.
It contains the following top-level sections: exporters, samplers, processors,
readers, providers and signals.
4.1. Exporters
---------------
Each exporter has a user-chosen name and a 'type' that determines which
additional options are available. Options marked with (*) are required.
Supported types:
otlp_grpc - Export via OTLP over gRPC.
type (*) "otlp_grpc"
thread_name exporter thread name (string)
endpoint OTLP/gRPC endpoint URL (string)
use_ssl_credentials enable SSL channel credentials (boolean)
ssl_credentials_cacert_path CA certificate file path (string)
ssl_credentials_cacert_as_string CA certificate as inline string (string)
ssl_client_key_path client private key file path (string)
ssl_client_key_string client private key as inline string (string)
ssl_client_cert_path client certificate file path (string)
ssl_client_cert_string client certificate as inline string (string)
timeout export timeout in seconds (integer)
user_agent User-Agent header value (string)
max_threads maximum exporter threads (integer)
compression compression algorithm name (string)
max_concurrent_requests concurrent request limit (integer)
otlp_http - Export via OTLP over HTTP (JSON or Protobuf).
type (*) "otlp_http"
thread_name exporter thread name (string)
endpoint OTLP/HTTP endpoint URL (string)
content_type payload format: "json" or "protobuf"
json_bytes_mapping binary encoding: "hexid", "utf8" or "base64"
debug enable debug output (boolean)
timeout export timeout in seconds (integer)
http_headers custom HTTP headers (list of key: value)
max_concurrent_requests concurrent request limit (integer)
max_requests_per_connection request limit per connection (integer)
ssl_insecure_skip_verify skip TLS certificate verification (boolean)
ssl_ca_cert_path CA certificate file path (string)
ssl_ca_cert_string CA certificate as inline string (string)
ssl_client_key_path client private key file path (string)
ssl_client_key_string client private key as inline string (string)
ssl_client_cert_path client certificate file path (string)
ssl_client_cert_string client certificate as inline string (string)
ssl_min_tls minimum TLS version (string)
ssl_max_tls maximum TLS version (string)
ssl_cipher TLS cipher list (string)
ssl_cipher_suite TLS 1.3 cipher suite list (string)
compression compression algorithm name (string)
otlp_file - Export to local files in OTLP format.
type (*) "otlp_file"
thread_name exporter thread name (string)
file_pattern output filename pattern (string)
alias_pattern symlink pattern for latest file (string)
flush_interval flush interval in microseconds (integer)
flush_count spans per flush (integer)
file_size maximum file size in bytes (integer)
rotate_size number of rotated files to keep (integer)
ostream - Write to a file (text output, useful for debugging).
type (*) "ostream"
filename output file path (string)
memory - In-memory buffer (useful for testing).
type (*) "memory"
buffer_size maximum buffered items (integer)
zipkin - Export to Zipkin-compatible backends.
type (*) "zipkin"
endpoint Zipkin collector URL (string)
format payload format: "json" or "protobuf"
service_name service name reported to Zipkin (string)
ipv4 service IPv4 address (string)
ipv6 service IPv6 address (string)
elasticsearch - Export to Elasticsearch.
type (*) "elasticsearch"
host Elasticsearch hostname (string)
port Elasticsearch port (integer)
index Elasticsearch index name (string)
response_timeout response timeout in seconds (integer)
debug enable debug output (boolean)
http_headers custom HTTP headers (list of key: value)
4.2. Samplers
--------------
Samplers control which traces are recorded. Each sampler has a user-chosen
name and a 'type' that determines its behavior.
Supported types:
always_on - Sample every trace.
type (*) "always_on"
always_off - Sample no traces.
type (*) "always_off"
trace_id_ratio_based - Sample a fraction of traces.
type (*) "trace_id_ratio_based"
ratio sampling ratio, 0.0 to 1.0 (float)
parent_based - Inherit sampling decision from parent span.
type (*) "parent_based"
delegate fallback sampler name (string)
4.3. Processors
----------------
Processors define how telemetry data is handled before export. Each
processor has a user-chosen name and a 'type' that determines its behavior.
Supported types:
batch - Batch spans before exporting.
type (*) "batch"
thread_name processor thread name (string)
max_queue_size maximum queued spans (integer)
schedule_delay export interval in milliseconds (integer)
max_export_batch_size maximum spans per export call (integer)
When the queue reaches half capacity, a preemptive notification triggers
an early export.
single - Export each span individually (no batching).
type (*) "single"
4.4. Readers
-------------
Readers define how metrics are collected and exported. Each reader has a
user-chosen name.
thread_name reader thread name (string)
export_interval collection interval in milliseconds (integer)
export_timeout export timeout in milliseconds (integer)
4.5. Providers
---------------
Providers define resource attributes attached to all telemetry data. Each
provider has a user-chosen name.
resources key-value resource attributes (list)
Standard resource attribute keys include service.name, service.version,
service.instance.id and service.namespace.
4.6. Signals
-------------
Signals bind exporters, samplers, processors, readers and providers together
for each telemetry type. The supported signal names are "traces", "metrics"
and "logs".
scope_name instrumentation scope name (string)
exporters exporter name reference (string)
samplers sampler name reference (string, traces only)
processors processor name reference (string, traces/logs)
readers reader name reference (string, metrics only)
providers provider name reference (string)
min_severity minimum log severity level (string, logs only)
The "min_severity" option controls which log records are emitted. Only log
records whose severity is equal to or higher than the configured minimum are
passed to the exporter. The value is a severity name as listed under the
"log-record" keyword (e.g. "trace", "debug", "info", "warn", "error", "fatal").
If omitted, the logger accepts all severity levels.
5. HAProxy rule integration
----------------------------
Groups defined in the OTel configuration file can be triggered from HAProxy
TCP/HTTP rules using the 'otel-group' action keyword:
http-request otel-group <filter-id> <group> [condition]
http-response otel-group <filter-id> <group> [condition]
http-after-response otel-group <filter-id> <group> [condition]
tcp-request otel-group <filter-id> <group> [condition]
tcp-response otel-group <filter-id> <group> [condition]
This allows running specific groups of scopes based on ACL conditions defined
in the HAProxy configuration.
Example (from test/sa/haproxy.cfg):
acl acl-http-status-ok status 100:399
filter opentelemetry id otel-test-sa config sa/otel.cfg
# Run response scopes for successful responses
http-response otel-group otel-test-sa http_response_group if acl-http-status-ok
# Run after-response scopes for error responses
http-after-response otel-group otel-test-sa http_after_response_group if !acl-http-status-ok
6. Complete examples
---------------------
The test directory contains several complete example configurations. Each
subdirectory contains an OTel configuration file (otel.cfg), a YAML file
(otel.yml) and a HAProxy configuration file (haproxy.cfg).
6.1. Standalone example (sa)
------------------------------
The most comprehensive example. All possible events are used, with spans,
attributes, events, links, baggage, status, metrics and groups demonstrated.
--- test/sa/otel.cfg (excerpt) -----------------------------------------
[otel-test-sa]
otel-instrumentation otel-test-instrumentation
config sa/otel.yml
option dontlog-normal
option hard-errors
no option disabled
rate-limit 100.0
groups http_response_group
groups http_after_response_group
scopes on_stream_start
scopes on_stream_stop
scopes client_session_start
scopes frontend_tcp_request
...
scopes server_session_end
otel-group http_response_group
scopes http_response_1
scopes http_response_2
otel-scope http_response_1
span "HTTP response"
event "event_content" "hdr.content" res.hdr("content-type") str("; length: ") res.hdr("content-length") str(" bytes")
otel-scope on_stream_start
instrument udcnt_int "haproxy.sessions.active" desc "Active sessions" value int(1) unit "{session}"
span "HAProxy session" root
baggage "haproxy_id" var(sess.otel.uuid)
event "event_ip" "src" src str(":") src_port
acl acl-test-src-ip src 127.0.0.1
otel-event on-stream-start if acl-test-src-ip
otel-scope on_stream_stop
finish *
otel-event on-stream-stop
otel-scope client_session_start
span "Client session" parent "HAProxy session"
otel-event on-client-session-start
otel-scope frontend_http_request
span "Frontend HTTP request" parent "HTTP body request" link "HAProxy session"
attribute "http.method" method
attribute "http.url" url
attribute "http.version" str("HTTP/") req.ver
finish "HTTP body request"
otel-event on-frontend-http-request
otel-scope server_session_start
span "Server session" parent "HAProxy session"
link "HAProxy session" "Client session"
finish "Process sticking rules request"
otel-event on-server-session-start
otel-scope server_session_end
finish *
otel-event on-server-session-end
---------------------------------------------------------------------
6.2. Frontend / backend example (fe/be)
-----------------------------------------
Demonstrates distributed tracing across two cascaded HAProxy instances using
inject/extract to propagate the span context via HTTP headers.
The frontend HAProxy (test/fe) creates the root trace and injects context:
--- test/fe/otel.cfg (excerpt) -----------------------------------------
otel-scope backend_http_request
span "Backend HTTP request" parent "Backend TCP request"
finish "Backend TCP request"
span "HAProxy session"
inject "otel-ctx" use-headers
otel-event on-backend-http-request
---------------------------------------------------------------------
The backend HAProxy (test/be) extracts the context and continues the trace:
--- test/be/otel.cfg (excerpt) -----------------------------------------
otel-scope frontend_http_request
extract "otel-ctx" use-headers
span "HAProxy session" parent "otel-ctx" root
baggage "haproxy_id" var(sess.otel.uuid)
span "Client session" parent "HAProxy session"
span "Frontend HTTP request" parent "Client session"
attribute "http.method" method
attribute "http.url" url
attribute "http.version" str("HTTP/") req.ver
otel-event on-frontend-http-request
---------------------------------------------------------------------
6.3. Context propagation example (ctx)
----------------------------------------
Similar to 'sa', but spans are opened using extracted span contexts as parent
references instead of direct span names. This demonstrates the inject/extract
mechanism using HAProxy variables.
--- test/ctx/otel.cfg (excerpt) ----------------------------------------
otel-scope client_session_start_1
span "HAProxy session" root
inject "otel_ctx_1" use-headers use-vars
baggage "haproxy_id" var(sess.otel.uuid)
otel-event on-client-session-start
otel-scope client_session_start_2
extract "otel_ctx_1" use-vars
span "Client session" parent "otel_ctx_1"
inject "otel_ctx_2" use-headers use-vars
otel-event on-client-session-start
otel-scope frontend_tcp_request
extract "otel_ctx_2" use-vars
span "Frontend TCP request" parent "otel_ctx_2"
inject "otel_ctx_3" use-headers use-vars
otel-event on-frontend-tcp-request
otel-scope http_wait_request
extract "otel_ctx_3" use-vars
span "HTTP wait request" parent "otel_ctx_3"
finish "Frontend TCP request" "otel_ctx_3"
otel-event on-http-wait-request
---------------------------------------------------------------------
6.4. Comparison example (cmp)
-------------------------------
A configuration made for comparison purposes with other tracing implementations.
It uses a simplified span hierarchy without context propagation.
--- test/cmp/otel.cfg (excerpt) ----------------------------------------
otel-scope client_session_start
span "HAProxy session" root
baggage "haproxy_id" var(sess.otel.uuid)
span "Client session" parent "HAProxy session"
otel-event on-client-session-start
otel-scope http_response-error
span "HTTP response"
status "error" str("!acl-http-status-ok")
otel-event on-http-response if !acl-http-status-ok
otel-scope server_session_end
finish "HTTP response" "Server session"
otel-event on-http-response
otel-scope client_session_end
finish "*"
otel-event on-http-response
---------------------------------------------------------------------
6.5. Empty / minimal example (empty)
--------------------------------------
The minimal valid OTel configuration. The filter is initialized but no events
are triggered:
--- test/empty/otel.cfg -------------------------------------------------
otel-instrumentation otel-test-instrumentation
config empty/otel.yml
---------------------------------------------------------------------
This is useful for testing the OTel filter initialization behavior without any
actual telemetry processing.

View file

@ -1,725 +0,0 @@
OpenTelemetry filter -- design patterns
==========================================================================
This document describes the cross-cutting design patterns used throughout
the OTel filter implementation. It complements README-implementation
(component-by-component architecture) with a pattern-oriented view of the
mechanisms that span multiple source files.
1 X-Macro Code Generation
----------------------------------------------------------------------
The filter uses X-macro lists extensively to generate enums, keyword tables,
event metadata and configuration init/free functions from a single definition.
Each X-macro list is a preprocessor define containing repeated invocations of
a helper macro whose name is supplied by the expansion context.
1.1 Event Definitions
FLT_OTEL_EVENT_DEFINES (event.h) drives the event system. Each entry has the
form:
FLT_OTEL_EVENT_DEF(NAME, CHANNEL, REQ_AN, RES_AN, HTTP_INJ, "string")
The same list is expanded twice:
- In enum FLT_OTEL_EVENT_enum (event.h) to produce symbolic constants
(FLT_OTEL_EVENT_NONE, FLT_OTEL_EVENT_REQ_STREAM_START, etc.) followed by
the FLT_OTEL_EVENT_MAX sentinel.
- In the flt_otel_event_data[] initializer (event.c) to produce a const table
of struct flt_otel_event_data entries carrying the analyzer bit, sample
fetch direction, valid fetch locations, HTTP injection flag and event name
string.
Adding a new event requires a single line in the X-macro list.
1.2 Parser Keyword Definitions
Three X-macro lists drive the configuration parser:
FLT_OTEL_PARSE_INSTR_DEFINES (parser.h, 9 keywords)
FLT_OTEL_PARSE_GROUP_DEFINES (parser.h, 2 keywords)
FLT_OTEL_PARSE_SCOPE_DEFINES (parser.h, 15 keywords)
Each entry has the form:
FLT_OTEL_PARSE_DEF(ENUM, flag, check, min, max, "name", "usage")
Each list is expanded to:
- An enum for keyword indices (FLT_OTEL_PARSE_INSTR_ID, etc.).
- A static parse_data[] table of struct flt_otel_parse_data entries carrying
the keyword index, flag_check_id, check_name type, argument count bounds,
keyword name string and usage string.
The section parsers (flt_otel_parse_cfg_instr, _group, _scope) look up args[0]
in their parse_data table via flt_otel_parse_cfg_check(), which validates
argument counts and character constraints before dispatching to
the keyword-specific handler.
Additional X-macro lists generate:
FLT_OTEL_PARSE_SCOPE_STATUS_DEFINES Span status codes.
FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEFINES Instrument type keywords.
FLT_OTEL_HTTP_METH_DEFINES HTTP method name table.
FLT_OTEL_GROUP_DEFINES Group action metadata.
1.3 Configuration Init/Free Generation
Two macros in conf_funcs.h generate init and free functions for all
configuration structure types:
FLT_OTEL_CONF_FUNC_INIT(_type_, _id_, _func_)
FLT_OTEL_CONF_FUNC_FREE(_type_, _id_, _func_)
The _type_ parameter names the structure (e.g. "span"), _id_ names the
identifier field within the FLT_OTEL_CONF_HDR (e.g. "id", "key", "str"), and
_func_ is a brace-enclosed code block executed after the boilerplate allocation
(for init) or before the boilerplate teardown (for free).
The generated init function:
1. Validates the identifier is non-NULL and non-empty.
2. Checks the length against FLT_OTEL_ID_MAXLEN (64).
3. Detects duplicates in the target list.
4. Handles auto-generated IDs (FLT_OTEL_CONF_HDR_SPECIAL prefix).
5. Allocates with OTELC_CALLOC, duplicates the ID with OTELC_STRDUP.
6. Appends to the parent list via LIST_APPEND.
7. Executes the custom _func_ body.
The generated free function:
1. Executes the custom _func_ body (typically list destruction).
2. Frees the identifier string.
3. Removes the node from its list.
4. Frees the structure with OTELC_SFREE_CLEAR.
conf.c instantiates these macros for all 13 configuration types: hdr, str, ph,
sample_expr, sample, link, context, span, scope, instrument, log_record, group,
instr. The more complex types (span, scope, instr) carry non-trivial _func_
bodies that initialize sub-lists or set default values.
2 Type-Safe Generic Macros
----------------------------------------------------------------------
define.h provides utility macros that use typeof() and statement expressions
for type-safe generic programming without C++ templates.
2.1 Safe Pointer Dereferencing
FLT_OTEL_PTR_SAFE(a, b)
Returns 'a' if non-NULL, otherwise the default value 'b'. Uses typeof(*(a))
to verify type compatibility at compile time.
FLT_OTEL_DEREF(p, m, v)
Safely dereferences p->m, returning 'v' if 'p' is NULL.
FLT_OTEL_DDEREF(a, m, v)
Safely double-dereferences *a->m, returning 'v' if either 'a' or '*a' is
NULL.
These macros eliminate repetitive NULL checks while preserving the correct
pointer types through typeof().
2.2 String Comparison
FLT_OTEL_STR_CMP(S, s)
Compares a runtime string 's' against a compile-time string literal 'S'.
Expands to a call with the literal's address and computed length, avoiding
repeated strlen() on known constants.
FLT_OTEL_CONF_STR_CMP(s, S)
Compares two configuration strings using their cached length fields (from
FLT_OTEL_CONF_STR / FLT_OTEL_CONF_HDR). Falls through to memcmp() only when
lengths match, providing an early-out fast path.
2.3 List Operations
FLT_OTEL_LIST_ISVALID(a)
Checks whether a list head has been initialized (both prev and next are
non-NULL).
FLT_OTEL_LIST_DEL(a)
Safely deletes a list element only if the list head is valid.
FLT_OTEL_LIST_DESTROY(t, h)
Destroys all elements in a typed configuration list by iterating with
list_for_each_entry_safe and calling flt_otel_conf_<t>_free() for each
entry. The type 't' is used to derive both the structure type and the
free function name.
2.4 Thread-Local Rotating Buffers
FLT_OTEL_BUFFER_THR(b, m, n, p)
Declares a thread-local pool of 'n' string buffers, each of size 'm'.
The pool index rotates on each invocation, avoiding the need for explicit
allocation in debug formatting functions. Each call returns a pointer to
the next buffer via the output parameter 'p'.
Used primarily in debug-only functions (flt_otel_analyzer,
flt_otel_list_dump) where temporary strings must survive until their caller
finishes with them.
3 Error Handling Architecture
----------------------------------------------------------------------
3.1 Error Accumulation Macros
Three macros in define.h manage error messages:
FLT_OTEL_ERR(f, ...)
Formats an error message via memprintf() into the caller's char **err
pointer, but only if *err is still NULL. Prevents overwriting the first
error with a cascading secondary error.
FLT_OTEL_ERR_APPEND(f, ...)
Unconditionally appends to the error message regardless of prior content.
Used when a function must report multiple issues.
FLT_OTEL_ERR_FREE(p)
Logs the accumulated error message at WARNING level via FLT_OTEL_ALERT,
then frees the string and NULLs the pointer.
All source functions that can fail accept a char **err parameter. The
convention is: set *err on error, leave it alone on success. Callers check
both the return value and *err to detect problems.
3.2 Parse-Time Error Reporting
Configuration parsing (parser.c) uses additional macros:
FLT_OTEL_PARSE_ERR(e, f, ...)
Sets the error string and ORs ERR_ABORT | ERR_ALERT into the return value.
Used inside section parser switch statements.
FLT_OTEL_PARSE_ALERT(f, ...)
Issues a ha_alert() with the current file and line, then sets the error
flags. Used when the error message should appear immediately.
FLT_OTEL_POST_PARSE_ALERT(f, ...)
Same as PARSE_ALERT but uses cfg_file instead of file, for post-parse
validation that runs after the parser has moved on.
FLT_OTEL_PARSE_IFERR_ALERT()
Checks whether *err was set by a called function, and if so, issues an
alert and clears the error. This bridges between functions that use the
char **err convention and the parser's own alert mechanism.
3.3 Runtime Error Modes
Two helper functions in filter.c implement the runtime error policy:
flt_otel_return_int(f, err, retval)
flt_otel_return_void(f, err)
If an error is detected (retval == FLT_OTEL_RET_ERROR or *err != NULL):
Hard-error mode (rt_ctx->flag_harderr):
Sets rt_ctx->flag_disabled = 1, disabling the filter for the remainder of
the stream. Atomically increments the disabled counter. The error is
logged as "filter hard-error (disabled)".
Soft-error mode (default):
The error is logged as "filter soft-error" and processing continues. The
tracing data for the current event may be incomplete but the stream is not
affected.
In both modes, FLT_OTEL_RET_OK is always returned to HAProxy. A tracing failure
never interrupts stream processing.
3.4 Disabled State Checking
flt_otel_is_disabled() (filter.c) is called at the top of every filter callback
that processes events. It checks three conditions:
1. rt_ctx is NULL (filter was never attached or already detached).
2. rt_ctx->flag_disabled is set (hard-error disabled the filter).
3. conf->instr->flag_disabled is set (globally disabled via CLI).
All three are loaded via _HA_ATOMIC_LOAD() for thread safety. When disabled,
the callback returns immediately without processing.
4 Thread Safety Model
----------------------------------------------------------------------
The filter runs within HAProxy's multi-threaded worker model. Each stream is
bound to a single thread, so per-stream state (the runtime context, spans and
contexts) is accessed without locking. Cross-stream shared state uses atomic
operations.
4.1 Atomic Fields
The following fields are accessed atomically across threads:
conf->instr->flag_disabled Toggled by CLI "otel enable/disable". Checked in
flt_otel_ops_attach() and flt_otel_is_disabled().
conf->instr->flag_harderr Toggled by CLI "otel hard-errors/soft-errors".
Copied to rt_ctx at attach time.
conf->instr->rate_limit Set by CLI "otel rate".
Read in flt_otel_ops_attach().
conf->instr->logging Set by CLI "otel logging".
Copied to rt_ctx at attach time.
flt_otel_drop_cnt Incremented in flt_otel_log_handler_cb().
Read by CLI "otel status".
conf->cnt.disabled[] Incremented in flt_otel_return_int/void().
conf->cnt.attached[] Incremented in flt_otel_ops_attach().
All use _HA_ATOMIC_LOAD(), _HA_ATOMIC_STORE(), _HA_ATOMIC_INC() or
_HA_ATOMIC_ADD() from HAProxy's atomic primitives.
4.2 Metric Instrument Creation (CAS Pattern)
Metric instruments are shared across all threads but created lazily on first
use. The creation uses a compare-and-swap pattern to ensure exactly one thread
performs the creation:
int64_t expected = OTELC_METRIC_INSTRUMENT_UNSET; /* -1 */
if (HA_ATOMIC_CAS(&instr->idx, &expected, OTELC_METRIC_INSTRUMENT_PENDING)) {
/* This thread won the race -- create the instrument. */
idx = meter->create_instrument(...);
HA_ATOMIC_STORE(&instr->idx, idx);
}
The three states are:
UNSET (-1) Not yet created. The CAS target.
PENDING (-2) Creation in progress by another thread.
>= 0 Valid meter index. Ready for recording.
Threads that lose the CAS skip creation. Update-form instruments check that the
referenced create-form's idx is >= 0 before recording; if it is still PENDING or
UNSET, the measurement is silently skipped.
4.3 Per-Thread Initialization Guard
flt_otel_ops_init_per_thread() uses the FLT_CFG_FL_HTX flag on fconf as a
one-shot guard: the tracer, meter and logger background threads are started only
on the first call. Subsequent calls from other proxies sharing the same filter
configuration skip the start sequence.
4.4 CLI Update Propagation
CLI set commands (cli.c) iterate all OTel filter instances across all proxies
using the FLT_OTEL_PROXIES_LIST_START / FLT_OTEL_PROXIES_LIST_END macros
(util.h) and atomically update the target field on each instance. This ensures
that "otel disable" takes effect globally, not just on one proxy.
5 Sample Evaluation Pipeline
----------------------------------------------------------------------
Sample expressions bridge HAProxy's runtime data (headers, variables, connection
properties) into OTel attributes, events, baggage, status, metric values and log
record bodies.
5.1 Two Evaluation Paths
The parser detects which path to use based on the presence of "%[" in the sample
value argument:
Log-format path (sample->lf_used == true):
The value is parsed via parse_logformat_string() and stored in
sample->lf_expr. At runtime, build_logline() evaluates the expression into
a temporary buffer of global.tune.bufsize bytes. The result is always a
string.
Bare sample path (sample->lf_used == false):
Each whitespace-separated token is parsed via sample_parse_expr() and
stored as an flt_otel_conf_sample_expr in sample->exprs. At runtime, each
expression is evaluated via sample_process(). If there is exactly one
expression, the native HAProxy sample type is preserved (bool, int, IP
address, string). If there are multiple expressions, their string
representations are concatenated.
5.2 Type Conversion Chain
flt_otel_sample_to_value() (util.c) converts HAProxy sample types to OTel value
types:
SMP_T_BOOL -> OTELC_VALUE_BOOL
SMP_T_SINT -> OTELC_VALUE_INT64
Other -> OTELC_VALUE_DATA (string, via flt_otel_sample_to_str)
flt_otel_sample_to_str() handles the string conversion for non-trivial types:
SMP_T_BOOL "0" or "1"
SMP_T_SINT snprintf decimal
SMP_T_IPV4 inet_ntop
SMP_T_IPV6 inet_ntop
SMP_T_STR direct copy
SMP_T_METH lookup from static HTTP method table, or raw string for
HTTP_METH_OTHER
For metric instruments, string values are rejected -- the meter requires
OTELC_VALUE_INT64. If the sample evaluates to a string, otelc_value_strtonum()
attempts numeric conversion as a last resort.
5.3 Dispatch to Data Structures
flt_otel_sample_add() (util.c) is the top-level evaluator. After converting the
sample to an otelc_value, it dispatches based on type:
FLT_OTEL_EVENT_SAMPLE_ATTRIBUTE
-> flt_otel_sample_add_kv(&data->attributes, key, &value)
FLT_OTEL_EVENT_SAMPLE_BAGGAGE
-> flt_otel_sample_add_kv(&data->baggage, key, &value)
FLT_OTEL_EVENT_SAMPLE_EVENT
-> flt_otel_sample_add_event(&data->events, sample, &value)
Groups attributes by event name; creates a new flt_otel_scope_data_event
node if the event name is not yet present.
FLT_OTEL_EVENT_SAMPLE_STATUS
-> flt_otel_sample_set_status(&data->status, sample, &value, err)
Sets the status code from sample->extra.num and the description from the
evaluated value. Rejects duplicate status settings.
5.4 Growable Key-Value Arrays
Attribute and baggage arrays use a lazy-allocation / grow-on-demand pattern via
flt_otel_sample_add_kv() (util.c):
- Initial allocation: FLT_OTEL_ATTR_INIT_SIZE (8) elements via otelc_kv_new().
- Growth: FLT_OTEL_ATTR_INC_SIZE (4) additional elements via otelc_kv_resize()
when the array is full.
- The cnt field tracks used elements; size tracks total capacity.
Event attribute arrays follow the same pattern within each
flt_otel_scope_data_event node.
6 Rate Limiting and Filtering
----------------------------------------------------------------------
6.1 Rate Limit Representation
The rate limit is configured as a floating-point percentage (0.0 to 100.0) but
stored internally as a uint32_t for lock-free comparison:
FLT_OTEL_FLOAT_U32(a) Converts a percentage to a uint32 value:
(uint32_t)((a / 100.0) * UINT32_MAX)
FLT_OTEL_U32_FLOAT(a) Converts back for display:
((double)(a) / UINT32_MAX) * 100.0
At attach time, the filter compares a fresh ha_random32() value against the
stored rate_limit. If the random value exceeds the threshold, the filter
returns FLT_OTEL_RET_IGNORE and does not attach to the stream. This provides
uniform sampling without floating-point arithmetic on the hot path.
6.2 ACL-Based Filtering
Each scope may carry an ACL condition (if/unless) evaluated at scope
execution time via acl_exec_cond(). ACL references are resolved through
a priority chain in flt_otel_parse_acl() (parser.c):
1. Scope-local ACLs (declared within the otel-scope section).
2. Instrumentation ACLs (declared in the otel-instrumentation section).
3. Proxy ACLs (declared in the HAProxy frontend/backend section).
The first list that produces a successful build_acl_cond() result is used.
If the condition fails at runtime:
- If the scope contains a root span, the entire stream is disabled
(rt_ctx->flag_disabled = 1) to avoid orphaned child spans.
- Otherwise, the scope is simply skipped.
6.3 Global Disable
The filter can be disabled globally via the CLI ("otel disable") or the
configuration ("option disabled"). The flag_disabled field on the
instrumentation configuration is checked atomically in flt_otel_ops_attach()
before any per-stream allocation occurs.
7 Memory Management Strategy
----------------------------------------------------------------------
7.1 Pool-Based Allocation
Four HAProxy memory pools are registered for frequently allocated structures
(pool.c):
pool_head_otel_scope_span Per-stream span entries.
pool_head_otel_scope_context Per-stream context entries.
pool_head_otel_runtime_context Per-stream runtime context.
pool_head_otel_span_context OTel SDK objects (via C wrapper allocator
callback).
Pool registration is conditional on compile-time flags (USE_POOL_OTEL_* in
config.h). flt_otel_pool_alloc() attempts pool allocation first and falls back
to heap allocation (OTELC_MALLOC) when the pool is NULL or the requested size
exceeds the pool's element size.
The C wrapper library's memory allocations are redirected through
flt_otel_mem_malloc() / flt_otel_mem_free() (filter.c), which use the
otel_span_context pool via otelc_ext_init(). This keeps OTel SDK objects in
HAProxy's pool allocator for cache-friendly allocation.
7.2 Trash Buffers
flt_otel_trash_alloc() (pool.c) provides temporary scratch buffers of
global.tune.bufsize bytes. When USE_TRASH_CHUNK is defined, it uses HAProxy's
alloc_trash_chunk(); otherwise it manually allocates a buffer structure and data
area. Trash buffers are used for:
- Log-format evaluation in metric recording and log record emission.
- HTTP header name construction in flt_otel_http_header_set().
- Temporary string assembly in sample evaluation.
7.3 Scope Data Lifecycle
flt_otel_scope_data (scope.h) is initialized and freed within a single span
processing block in flt_otel_scope_run(). It is stack-conceptual data:
allocated at the start of each span's processing, populated during sample
evaluation, consumed by flt_otel_scope_run_span(), and freed immediately after.
The key-value arrays within it are heap-allocated via otelc_kv_new() and freed
via otelc_kv_destroy().
8 Context Propagation Mechanism
----------------------------------------------------------------------
8.1 Carrier Abstraction
The OTel C wrapper uses a carrier pattern for context propagation. Two carrier
types exist, each with writer and reader variants:
otelc_text_map_writer / otelc_text_map_reader
otelc_http_headers_writer / otelc_http_headers_reader
otelc.c provides callback functions that the C wrapper invokes during
inject/extract operations:
Injection (writer callbacks):
flt_otel_text_map_writer_set_cb()
flt_otel_http_headers_writer_set_cb()
Both append key-value pairs to the carrier's text_map via
OTELC_TEXT_MAP_ADD.
Extraction (reader callbacks):
flt_otel_text_map_reader_foreach_key_cb()
flt_otel_http_headers_reader_foreach_key_cb()
Both iterate the text_map's key-value pairs, invoking a handler function
for each entry. Iteration stops early if the handler returns -1.
8.2 HTTP Header Storage
flt_otel_http_headers_get() (http.c) reads headers from the HTX buffer with
prefix matching. The prefix is stripped from header names in the returned
text_map. A special prefix character ('-') matches all headers without prefix
filtering.
flt_otel_http_header_set() constructs a "prefix-name" header, removes all
existing occurrences via an http_find_header / http_remove_header loop, then
adds the new value via http_add_header.
8.3 Variable Storage
When USE_OTEL_VARS is enabled, span context can also be stored in HAProxy
transaction-scoped variables. Variable names are normalized
(flt_otel_normalize_name in vars.c):
Dashes -> 'D' (FLT_OTEL_VAR_CHAR_DASH)
Spaces -> 'S' (FLT_OTEL_VAR_CHAR_SPACE)
Uppercase -> lowercase
Full variable names are constructed as "scope.prefix.name" with dots as
component separators.
Two implementation paths exist based on the USE_OTEL_VARS_NAME compile flag:
With USE_OTEL_VARS_NAME:
A binary tracking buffer (stored as a HAProxy variable) records the names
of all context variables. flt_otel_ctx_loop() iterates length-prefixed
entries in this buffer, calling a callback for each. This enables efficient
enumeration without scanning the full variable store.
Without USE_OTEL_VARS_NAME:
Direct CEB tree traversal on the HAProxy variable store with prefix
matching. This is simpler but potentially slower for large variable sets.
The choice is auto-detected at build time by checking whether struct var has
a 'name' member.
9 Debug Infrastructure
----------------------------------------------------------------------
9.1 Conditional Compilation
When OTEL_DEBUG=1 is set at build time, -DDEBUG_OTEL is added to the compiler
flags. This enables:
- Additional flt_ops callbacks: deinit_per_thread, http_payload, http_reset,
tcp_payload. In non-debug builds these are NULL.
- FLT_OTEL_USE_COUNTERS (config.h), which activates the per-event counters in
flt_otel_counters (attached[], disabled[] arrays).
- Debug-only functions throughout the codebase, marked with [D] in
README-func: flt_otel_scope_data_dump, flt_otel_http_headers_dump,
flt_otel_vars_dump, flt_otel_args_dump, flt_otel_filters_dump, etc.
- The OTELC_DBG() macro for level-gated debug output.
9.2 Debug Level Bitmask
The debug level is a uint stored in conf->instr and controllable at runtime via
"otel debug <level>". The default value is FLT_OTEL_DEBUG_LEVEL (0b11101111111
in config.h). Each bit enables a category of debug output.
9.3 Logging Integration
The FLT_OTEL_LOG macro (debug.h) integrates with HAProxy's send_log() system.
Its behavior depends on the logging flags:
FLT_OTEL_LOGGING_OFF (0):
No log messages are emitted.
FLT_OTEL_LOGGING_ON (1 << 0):
Log messages are sent to the log servers configured in the instrumentation
block via parse_logger().
FLT_OTEL_LOGGING_NOLOGNORM (1 << 1):
Combined with ON, suppresses normal-level messages (only warnings and above
are emitted).
These flags are set via the "option dontlog-normal" configuration keyword or the
"otel logging" CLI command.
9.4 Counter System
When FLT_OTEL_USE_COUNTERS is defined, the flt_otel_counters structure (conf.h)
maintains:
attached[4] Counters for attach outcomes, incremented atomically in
flt_otel_ops_attach().
disabled[2] Counters for hard-error disables, incremented atomically in
flt_otel_return_int() / flt_otel_return_void().
The counters are reported by the "otel status" CLI command and dumped at deinit
time in debug builds.
10 Idle Timeout Mechanism
----------------------------------------------------------------------
The idle timeout fires periodic events on idle streams, enabling heartbeat-style
span updates or metric recordings.
10.1 Configuration
Each otel-scope bound to the "on-idle-timeout" event must declare an
idle-timeout interval. At check time (flt_otel_ops_check), the minimum idle
timeout across all scopes is stored in conf->instr->idle_timeout.
10.2 Initialization
In flt_otel_ops_stream_start(), the runtime context's idle_timeout and idle_exp
fields are initialized from the precomputed minimum. The expiration tick is
merged into the request channel's analyse_exp to ensure the stream task wakes
at the right time.
10.3 Firing
flt_otel_ops_check_timeouts() checks tick_is_expired(rt_ctx->idle_exp).
When expired:
- The on-idle-timeout event fires via flt_otel_event_run().
- The timer is rescheduled for the next interval.
- If analyse_exp itself has expired (which would cause a tight loop), it is
reset before the new idle_exp is merged.
- STRM_EVT_MSG is set on stream->pending_events to ensure the stream is
re-evaluated.
11 Group Action Integration Pattern
----------------------------------------------------------------------
The "otel-group" action integrates OTel scopes with HAProxy's rule system
(http-request, http-response, http-after-response, tcp-request, tcp-response).
11.1 Registration
group.c registers action keywords via INITCALL1 macros for all five action
contexts. Each registration points to flt_otel_group_parse() as the parse
function.
11.2 Parse-Check-Execute Cycle
Parse (flt_otel_group_parse):
Stores the filter ID and group ID as string duplicates in rule->arg.act.p[].
Sets the check, action and release callbacks.
Check (flt_otel_group_check):
Resolves the string IDs to configuration pointers by scanning the proxy's
filter list. Replaces the string pointers with resolved flt_conf,
flt_otel_conf and flt_otel_conf_ph pointers. Frees the original string
duplicates.
Execute (flt_otel_group_action):
Finds the filter instance in the stream's filter list. Validates rule->from
against the flt_otel_group_data[] table to determine the sample fetch
direction and valid fetch locations. Iterates all scopes in the group and
calls flt_otel_scope_run() for each. Always returns ACT_RET_CONT -- a group
action never interrupts rule processing.
11.3 Group Data Table
The flt_otel_group_data[] table (group.c) maps each HAProxy action context
(ACT_F_*) to:
act_from The action context enum value.
smp_val The valid sample fetch location (FE or BE).
smp_opt_dir The sample fetch direction (REQ or RES).
This table is generated from the FLT_OTEL_GROUP_DEFINES X-macro list (group.h).
12 CLI Runtime Control Architecture
----------------------------------------------------------------------
cli.c registers commands under the "otel" prefix. The command table maps
keyword sequences to handler functions with access level requirements.
12.1 Command Table
"otel status" No access restriction. Read-only status report.
"otel enable" ACCESS_LVL_ADMIN. Clears flag_disabled.
"otel disable" ACCESS_LVL_ADMIN. Sets flag_disabled.
"otel hard-errors" ACCESS_LVL_ADMIN. Sets flag_harderr.
"otel soft-errors" ACCESS_LVL_ADMIN. Clears flag_harderr.
"otel logging" ACCESS_LVL_ADMIN for setting; any for reading.
"otel rate" ACCESS_LVL_ADMIN for setting; any for reading.
"otel debug" ACCESS_LVL_ADMIN. DEBUG_OTEL only.
The "otel enable" and "otel disable" commands share the same handler
(flt_otel_cli_parse_disabled) with the private argument distinguishing the
operation (1 for disable, 0 for enable). The same pattern is used for
hard-errors / soft-errors.
12.2 Global Propagation
All set operations iterate every OTel filter instance across all proxies using
FLT_OTEL_PROXIES_LIST_START / FLT_OTEL_PROXIES_LIST_END macros and atomically
update the target field. A single "otel disable" command disables the filter
in every proxy that has it configured.
12.3 Status Report
The "otel status" command assembles a dynamic string via memprintf() containing:
- Library versions (C++ SDK and C wrapper).
- Debug level (DEBUG_OTEL only).
- Dropped diagnostic message count.
- Per-proxy: config file, group/scope counts, instrumentation details, rate
limit, error mode, disabled state, logging state, analyzer bitmask, and
counters (if FLT_OTEL_USE_COUNTERS).

View file

@ -1,723 +0,0 @@
OpenTelemetry filter -- function reference
==========================================================================
Functions are grouped by source file. Functions marked with [D] are only
compiled when DEBUG_OTEL is defined.
src/filter.c
----------------------------------------------------------------------
Filter lifecycle callbacks and helpers registered in flt_otel_ops.
flt_otel_mem_malloc
Allocator callback for the OTel C wrapper library. Uses the HAProxy
pool_head_otel_span_context pool.
flt_otel_mem_free
Deallocator callback for the OTel C wrapper library.
flt_otel_log_handler_cb
Diagnostic callback for the OTel C wrapper library. Counts SDK internal
diagnostic messages.
flt_otel_thread_id
Returns the current HAProxy thread ID (tid).
flt_otel_lib_init
Initializes the OTel C wrapper library: verifies the library version,
constructs the configuration path, calls otelc_init(), and creates the
tracer, meter and logger instances.
flt_otel_is_disabled
Checks whether the filter instance is disabled for the current stream.
Logs the event name when DEBUG_OTEL is enabled.
flt_otel_return_int
Error handler for callbacks returning int. In hard-error mode, disables
the filter; in soft-error mode, clears the error and returns OK.
flt_otel_return_void
Error handler for callbacks returning void. Same logic as
flt_otel_return_int but without a return value.
flt_otel_ops_init
Filter init callback (flt_ops.init). Called once per proxy to initialize
the OTel library via flt_otel_lib_init() and register CLI keywords.
flt_otel_ops_deinit
Filter deinit callback (flt_ops.deinit). Destroys the tracer, meter and
logger, frees the configuration, and calls otelc_deinit().
flt_otel_ops_check
Filter check callback (flt_ops.check). Validates the parsed
configuration: checks for duplicate filter IDs, resolves group/scope
placeholder references, verifies root span count, and sets analyzer bits.
flt_otel_ops_init_per_thread
Per-thread init callback (flt_ops.init_per_thread). Starts the OTel
tracer thread and enables HTX filtering.
flt_otel_ops_deinit_per_thread [D]
Per-thread deinit callback (flt_ops.deinit_per_thread).
flt_otel_ops_attach
Filter attach callback (flt_ops.attach). Called when a filter instance is
attached to a stream. Applies rate limiting, creates the runtime context,
and sets analyzer bits.
flt_otel_ops_stream_start
Stream start callback (flt_ops.stream_start). Fires the
on-stream-start event before any channel processing begins. The channel
argument is NULL. After the event, initializes the idle timer in the
runtime context from the precomputed minimum idle_timeout in the
instrumentation configuration.
flt_otel_ops_stream_set_backend
Stream set-backend callback (flt_ops.stream_set_backend). Fires the
on-backend-set event when a backend is assigned to the stream.
flt_otel_ops_stream_stop
Stream stop callback (flt_ops.stream_stop). Fires the
on-stream-stop event after all channel processing ends. The channel
argument is NULL.
flt_otel_ops_detach
Filter detach callback (flt_ops.detach). Frees the runtime context when
the filter is detached from a stream.
flt_otel_ops_check_timeouts
Timeout callback (flt_ops.check_timeouts). When the idle-timeout timer
has expired, fires the on-idle-timeout event and reschedules the timer
for the next interval. Sets the STRM_EVT_MSG pending event flag on the
stream.
flt_otel_ops_channel_start_analyze
Channel start-analyze callback. Registers analyzers on the channel and
runs the client/server session start event. Propagates the idle-timeout
expiry to the channel's analyse_exp so the stream task keeps waking.
flt_otel_ops_channel_pre_analyze
Channel pre-analyze callback. Maps the analyzer bit to an event index and
runs the corresponding event.
flt_otel_ops_channel_post_analyze
Channel post-analyze callback. Non-resumable; called once when a
filterable analyzer finishes.
flt_otel_ops_channel_end_analyze
Channel end-analyze callback. Runs the client/server session end event.
For the request channel, also fires the server-unavailable event if no
response was processed.
flt_otel_ops_http_headers
HTTP headers callback (flt_ops.http_headers). Fires
on-http-headers-request or on-http-headers-response depending on the
channel direction.
flt_otel_ops_http_payload [D]
HTTP payload callback (flt_ops.http_payload).
flt_otel_ops_http_end
HTTP end callback (flt_ops.http_end). Fires on-http-end-request or
on-http-end-response depending on the channel direction.
flt_otel_ops_http_reset [D]
HTTP reset callback (flt_ops.http_reset).
flt_otel_ops_http_reply
HTTP reply callback (flt_ops.http_reply). Fires the on-http-reply event
when HAProxy generates an internal reply.
flt_otel_ops_tcp_payload [D]
TCP payload callback (flt_ops.tcp_payload).
src/event.c
----------------------------------------------------------------------
Event dispatching, metrics recording and scope/span execution engine.
flt_otel_scope_run_instrument_record
Records a measurement for a synchronous metric instrument. Evaluates
update-form attributes via flt_otel_sample_eval() and
flt_otel_sample_add_kv(), evaluates the sample expression from the
create-form instrument (instr_ref), and submits the value to the meter
via update_instrument_kv_n().
flt_otel_scope_run_instrument
Processes all metric instruments for a scope. Runs in two passes: the
first lazily creates create-form instruments via the meter, using
HA_ATOMIC_CAS to guarantee thread-safe one-time initialization; the second
iterates update-form instruments and records measurements via
flt_otel_scope_run_instrument_record(). Instruments whose index is still
negative (UNUSED or PENDING) are skipped.
flt_otel_scope_run_log_record
Emits log records for a scope. Iterates over the configured log-record
list, skipping entries whose severity is below the logger threshold.
Evaluates the body from sample fetch expressions or a log-format string,
optionally resolves a span reference against the runtime context, and
emits the record via the logger. A missing span is non-fatal -- the
record is emitted without span correlation.
flt_otel_scope_run_span
Executes a single span: creates the OTel span on first call, adds links,
baggage, attributes, events and status, then injects the context into HTTP
headers or HAProxy variables.
flt_otel_scope_run
Executes a complete scope: evaluates ACL conditions, extracts contexts,
iterates over configured spans (resolving links, evaluating sample
expressions), calls flt_otel_scope_run_span for each, processes metric
instruments via flt_otel_scope_run_instrument(), emits log records via
flt_otel_scope_run_log_record(), then marks and finishes completed spans.
flt_otel_event_run
Top-level event dispatcher. Called from filter callbacks, iterates over
all scopes matching the event index and calls flt_otel_scope_run() for
each.
src/scope.c
----------------------------------------------------------------------
Runtime context, span and context lifecycle management.
flt_otel_pools_info [D]
Logs the sizes of all registered HAProxy memory pools used by the OTel
filter.
flt_otel_runtime_context_init
Allocates and initializes the per-stream runtime context. Generates a
UUID and stores it in the sess.otel.uuid HAProxy variable.
flt_otel_runtime_context_free
Frees the runtime context: ends all active spans, destroys all extracted
contexts, and releases pool memory.
flt_otel_scope_span_init
Finds an existing scope span by name or creates a new one. Resolves the
parent reference (span or extracted context).
flt_otel_scope_span_free
Frees a scope span entry if its OTel span has been ended. Refuses to free
an active (non-NULL) span.
flt_otel_scope_context_init
Finds an existing scope context by name or creates a new one by extracting
the span context from a text map.
flt_otel_scope_context_free
Frees a scope context entry and destroys the underlying OTel span context.
flt_otel_scope_data_dump [D]
Dumps scope data contents (baggage, attributes, events, links, status) for
debugging.
flt_otel_scope_data_init
Zero-initializes a scope data structure and its event/link lists.
flt_otel_scope_data_free
Frees all scope data contents: key-value arrays, event entries, link
entries, and status description.
flt_otel_scope_finish_mark
Marks spans and contexts for finishing. Supports wildcard ("*"),
channel-specific ("req"/"res"), and named targets.
flt_otel_scope_finish_marked
Ends all spans and destroys all contexts that have been marked for
finishing by flt_otel_scope_finish_mark().
flt_otel_scope_free_unused
Removes scope spans with NULL OTel span and scope contexts with NULL OTel
context. Cleans up associated HTTP headers and variables.
src/parser.c
----------------------------------------------------------------------
Configuration file parsing for otel-instrumentation, otel-group and otel-scope
sections.
flt_otel_parse_strdup
Duplicates a string with error handling; optionally stores the string
length.
flt_otel_parse_keyword
Parses a single keyword argument: checks for duplicates and missing
values, then stores via flt_otel_parse_strdup().
flt_otel_parse_invalid_char
Validates characters in a name according to the specified type
(identifier, domain, context prefix, variable).
flt_otel_parse_cfg_check
Common validation for config keywords: looks up the keyword, checks
argument count and character validity, verifies that the parent section ID
is set.
flt_otel_parse_cfg_sample_expr
Parses a single HAProxy sample expression within a sample definition.
Calls sample_parse_expr().
flt_otel_parse_cfg_sample
Parses a complete sample definition (key plus one or more sample
expressions).
flt_otel_parse_cfg_str
Parses one or more string arguments into a conf_str list (used for the
"finish" keyword).
flt_otel_parse_cfg_file
Parses and validates a file path argument; checks that the file exists and
is readable.
flt_otel_parse_check_scope
Checks whether the current config parsing is within the correct HAProxy
configuration scope (cfg_scope filtering).
flt_otel_parse_cfg_instr
Section parser for the otel-instrumentation block. Handles keywords:
otel-instrumentation ID, log, config, groups, scopes, acl, rate-limit,
option, debug-level.
flt_otel_post_parse_cfg_instr
Post-parse callback for otel-instrumentation. Links the instrumentation
to the config and checks that a config file is specified.
flt_otel_parse_cfg_group
Section parser for the otel-group block. Handles keywords: otel-group ID,
scopes.
flt_otel_post_parse_cfg_group
Post-parse callback for otel-group. Checks that at least one scope is
defined.
flt_otel_parse_cfg_scope_ctx
Parses the context storage type argument ("use-headers" or "use-vars") for
inject/extract keywords.
flt_otel_parse_acl
Builds an ACL condition by trying multiple ACL lists in order
(scope-local, instrumentation, proxy).
flt_otel_parse_bounds
Parses a space-separated string of numbers into a dynamically allocated
array of doubles for histogram bucket boundaries. Sorts the values
internally.
flt_otel_parse_cfg_instrument
Parses the "instrument" keyword inside an otel-scope section. Supports
both "update" form (referencing an existing instrument) and "create" form
(defining a new metric instrument with type, name, optional aggregation
type, description, unit, value, and optional histogram bounds).
flt_otel_parse_cfg_scope
Section parser for the otel-scope block. Handles keywords: otel-scope ID,
span, link, attribute, event, baggage, status, inject, extract, finish,
instrument, log-record, acl, otel-event.
flt_otel_post_parse_cfg_scope
Post-parse callback for otel-scope. Checks that HTTP header injection is
only used on events that support it.
flt_otel_parse_cfg
Parses the OTel filter configuration file. Backs up current sections,
registers temporary otel-instrumentation/group/scope section parsers,
loads and parses the file, then restores the original sections.
flt_otel_parse
Main filter parser entry point, registered for the "otel" filter keyword.
Parses the filter ID and configuration file path from the HAProxy config
line.
src/conf.c
----------------------------------------------------------------------
Configuration structure allocation and deallocation. Most init/free pairs are
generated by the FLT_OTEL_CONF_FUNC_INIT and FLT_OTEL_CONF_FUNC_FREE macros.
flt_otel_conf_hdr_init
Allocates and initializes a conf_hdr structure.
flt_otel_conf_hdr_free
Frees a conf_hdr structure and removes it from its list.
flt_otel_conf_str_init
Allocates and initializes a conf_str structure.
flt_otel_conf_str_free
Frees a conf_str structure and removes it from its list.
flt_otel_conf_link_init
Allocates and initializes a conf_link structure (span link).
flt_otel_conf_link_free
Frees a conf_link structure and removes it from its list.
flt_otel_conf_ph_init
Allocates and initializes a conf_ph (placeholder) structure.
flt_otel_conf_ph_free
Frees a conf_ph structure and removes it from its list.
flt_otel_conf_sample_expr_init
Allocates and initializes a conf_sample_expr structure.
flt_otel_conf_sample_expr_free
Frees a conf_sample_expr structure and releases the parsed sample
expression.
flt_otel_conf_sample_init
Allocates and initializes a conf_sample structure.
flt_otel_conf_sample_init_ex
Extended sample initialization: sets the key, extra data (event name or
status code), concatenated value string, and expression count.
flt_otel_conf_sample_free
Frees a conf_sample structure including its value, extra data, and all
sample expressions.
flt_otel_conf_context_init
Allocates and initializes a conf_context structure.
flt_otel_conf_context_free
Frees a conf_context structure and removes it from its list.
flt_otel_conf_span_init
Allocates and initializes a conf_span structure with empty lists for
links, attributes, events, baggages and statuses.
flt_otel_conf_span_free
Frees a conf_span structure and all its child lists.
flt_otel_conf_instrument_init
Allocates and initializes a conf_instrument structure.
flt_otel_conf_instrument_free
Frees a conf_instrument structure and removes it from its list.
flt_otel_conf_log_record_init
Allocates and initializes a conf_log_record structure with empty
attributes and samples lists.
flt_otel_conf_log_record_free
Frees a conf_log_record structure: event_name, span, attributes and
samples list.
flt_otel_conf_scope_init
Allocates and initializes a conf_scope structure with empty lists for
ACLs, contexts, spans, spans_to_finish and instruments.
flt_otel_conf_scope_free
Frees a conf_scope structure, ACLs, condition, and all child lists.
flt_otel_conf_group_init
Allocates and initializes a conf_group structure with an empty placeholder
scope list.
flt_otel_conf_group_free
Frees a conf_group structure and its placeholder scope list.
flt_otel_conf_instr_init
Allocates and initializes a conf_instr structure. Sets the default rate
limit to 100%, initializes the proxy_log, and creates empty ACL and
placeholder lists.
flt_otel_conf_instr_free
Frees a conf_instr structure including ACLs, loggers, config path, and
placeholder lists.
flt_otel_conf_init
Allocates and initializes the top-level flt_otel_conf structure with empty
group and scope lists.
flt_otel_conf_free
Frees the top-level flt_otel_conf structure and all of its children
(instrumentation, groups, scopes).
src/cli.c
----------------------------------------------------------------------
HAProxy CLI command handlers for runtime filter management.
cmn_cli_set_msg
Sets the CLI appctx response message and state.
flt_otel_cli_parse_debug [D]
CLI handler for "otel debug [level]". Gets or sets the debug level.
flt_otel_cli_parse_disabled
CLI handler for "otel enable" and "otel disable".
flt_otel_cli_parse_option
CLI handler for "otel soft-errors" and "otel hard-errors".
flt_otel_cli_parse_logging
CLI handler for "otel logging [state]". Gets or sets the logging state
(off/on/nolognorm).
flt_otel_cli_parse_rate
CLI handler for "otel rate [value]". Gets or sets the rate limit
percentage.
flt_otel_cli_parse_status
CLI handler for "otel status". Displays filter configuration and runtime
state for all OTel filter instances.
flt_otel_cli_init
Registers the OTel CLI keywords with HAProxy.
src/otelc.c
----------------------------------------------------------------------
OpenTelemetry context propagation bridge (inject/extract) between HAProxy and
the OTel C wrapper library.
flt_otel_text_map_writer_set_cb
Writer callback for text map injection. Appends a key-value pair to the
text map.
flt_otel_http_headers_writer_set_cb
Writer callback for HTTP headers injection. Appends a key-value pair to
the text map.
flt_otel_inject_text_map
Injects span context into a text map carrier.
flt_otel_inject_http_headers
Injects span context into an HTTP headers carrier.
flt_otel_text_map_reader_foreach_key_cb
Reader callback for text map extraction. Iterates over all key-value
pairs in the text map.
flt_otel_http_headers_reader_foreach_key_cb
Reader callback for HTTP headers extraction. Iterates over all key-value
pairs in the text map.
flt_otel_extract_text_map
Extracts a span context from a text map carrier via the tracer.
flt_otel_extract_http_headers
Extracts a span context from an HTTP headers carrier via the tracer.
src/http.c
----------------------------------------------------------------------
HTTP header manipulation for context propagation.
flt_otel_http_headers_dump [D]
Dumps all HTTP headers from the channel's HTX buffer.
flt_otel_http_headers_get
Extracts HTTP headers matching a prefix into a text map. Used by the
"extract" keyword to read span context from incoming request headers.
flt_otel_http_header_set
Sets or removes an HTTP header. Combines prefix and name into the full
header name, removes all existing occurrences, then adds the new value
(if non-NULL).
flt_otel_http_headers_remove
Removes all HTTP headers matching a prefix. Wrapper around
flt_otel_http_header_set() with NULL name and value.
src/vars.c
----------------------------------------------------------------------
HAProxy variable integration for context propagation and storage. Only compiled
when USE_OTEL_VARS is defined.
flt_otel_vars_scope_dump [D]
Dumps all variables for a single HAProxy variable scope.
flt_otel_vars_dump [D]
Dumps all variables across all scopes (PROC, SESS, TXN, REQ/RES).
flt_otel_smp_init
Initializes a sample structure with stream ownership and optional string
data.
flt_otel_smp_add
Appends a context variable name to the binary sample data buffer used for
tracking registered context variables.
flt_otel_normalize_name
Normalizes a variable name: replaces dashes with 'D' and spaces with 'S',
converts to lowercase.
flt_otel_denormalize_name
Reverses the normalization applied by flt_otel_normalize_name(). Restores
dashes from 'D' and spaces from 'S'.
flt_otel_var_name
Constructs a full variable name from scope, prefix and name components,
separated by dots.
flt_otel_ctx_loop
Iterates over all context variable names stored in the binary sample data,
calling a callback for each.
flt_otel_ctx_set_cb
Callback for flt_otel_ctx_loop() that checks whether a context variable
name already exists.
flt_otel_ctx_set
Registers a context variable name in the binary tracking buffer if it is
not already present.
flt_otel_var_register
Registers a HAProxy variable via vars_check_arg() so it can be used at
runtime.
flt_otel_var_set
Sets a HAProxy variable value. For context-scope variables, also
registers the name in the context tracking buffer.
flt_otel_vars_unset_cb
Callback for flt_otel_ctx_loop() that unsets each context variable.
flt_otel_vars_unset
Unsets all context variables for a given prefix and removes the tracking
variable itself.
flt_otel_vars_get_scope
Resolves a scope name string ("proc", "sess", "txn", "req", "res") to the
corresponding HAProxy variable store.
flt_otel_vars_get_cb
Callback for flt_otel_ctx_loop() that reads each context variable value
and adds it to a text map.
flt_otel_vars_get
Reads all context variables for a prefix into a text map. Used by the
"extract" keyword with variable storage.
src/pool.c
----------------------------------------------------------------------
Memory pool and trash buffer helpers.
flt_otel_pool_alloc
Allocates memory from a HAProxy pool (if available) or from the heap.
Optionally zero-fills the allocated block.
flt_otel_pool_strndup
Duplicates a string using a HAProxy pool (if available) or the heap.
flt_otel_pool_free
Returns memory to a HAProxy pool or frees it from the heap.
flt_otel_trash_alloc
Allocates a trash buffer chunk, optionally zero-filled.
flt_otel_trash_free
Frees a trash buffer chunk.
src/util.c
----------------------------------------------------------------------
Utility and conversion functions.
flt_otel_args_dump [D]
Dumps configuration arguments array to stderr.
flt_otel_filters_dump [D]
Dumps all OTel filter instances across all proxies.
flt_otel_chn_label [D]
Returns "REQuest" or "RESponse" based on channel flags.
flt_otel_pr_mode [D]
Returns "HTTP" or "TCP" based on proxy mode.
flt_otel_stream_pos [D]
Returns "frontend" or "backend" based on stream flags.
flt_otel_type [D]
Returns "frontend" or "backend" based on filter flags.
flt_otel_analyzer [D]
Returns the analyzer name string for a given analyzer bit.
flt_otel_list_dump [D]
Returns a summary string for a list (empty, single, count).
flt_otel_args_count
Counts the number of valid (non-NULL) arguments in an args array, handling
gaps from blank arguments.
flt_otel_args_concat
Concatenates arguments starting from a given index into a single
space-separated string.
flt_otel_strtod
Parses a string to double with range validation.
flt_otel_strtoll
Parses a string to int64 with range validation.
flt_otel_sample_to_str
Converts sample data to its string representation. Handles bool, sint,
IPv4, IPv6, str, and HTTP method types.
flt_otel_sample_to_value
Converts sample data to an otelc_value. Preserves native types (bool,
int64) where possible; falls back to string.
flt_otel_sample_add_event
Adds a sample value as a span event attribute. Groups attributes by event
name; dynamically grows the attribute array.
flt_otel_sample_set_status
Sets the span status code and description from sample data.
flt_otel_sample_add_kv
Adds a sample value as a key-value attribute or baggage entry.
Dynamically grows the key-value array.
flt_otel_sample_eval
Evaluates all sample expressions for a configured sample definition and
stores the result in an otelc_value. Supports both log-format and bare
sample expression paths. When flag_native is true and the sample has
exactly one expression, the native HAProxy sample type is preserved;
otherwise results are concatenated into a string.
flt_otel_sample_add
Top-level sample evaluator and dispatcher. Calls flt_otel_sample_eval()
to evaluate the sample, then dispatches the result to the appropriate
handler (attribute, event, baggage, status).
src/group.c
----------------------------------------------------------------------
Group action support for http-response / http-after-response / tcp-request /
tcp-response rules.
flt_otel_group_action
Action callback (action_ptr) for the otel-group rule. Finds the filter
instance on the current stream and runs all scopes defined in the group.
flt_otel_group_check
Check callback (check_ptr) for the otel-group rule. Resolves filter ID
and group ID references against the proxy's filter configuration.
flt_otel_group_release
Release callback (release_ptr) for the otel-group rule.
flt_otel_group_parse
Parses the "otel-group" action keyword from HAProxy config rules.
Registered for tcp-request, tcp-response, http-request, http-response and
http-after-response action contexts.

File diff suppressed because it is too large Load diff

View file

@ -1,101 +0,0 @@
OpenTelemetry filter -- miscellaneous notes
==============================================================================
1 Parsing sample expressions in HAProxy
------------------------------------------------------------------------------
HAProxy provides two entry points for turning a configuration string into an
evaluable sample expression.
1.1 sample_parse_expr()
..............................................................................
Parses a bare sample-fetch name with an optional converter chain. The input is
the raw expression without any surrounding syntax.
Declared in: include/haproxy/sample.h
Defined in: src/sample.c
struct sample_expr *sample_parse_expr(char **str, int *idx, const char *file, int line, char **err_msg, struct arg_list *al, char **endptr);
The function reads from str[*idx] and advances *idx past the consumed tokens.
Configuration example (otel-scope instrument keyword):
instrument my_counter "name" desc req.hdr(host),lower ...
Here "req.hdr(host),lower" is a single configuration token that
sample_parse_expr() receives directly. It recognises the fetch "req.hdr(host)"
and the converter "lower" separated by a comma.
1.2 parse_logformat_string()
..............................................................................
Parses a log-format string that may contain literal text mixed with sample
expressions wrapped in %[...] delimiters.
Declared in: include/haproxy/log.h
Defined in: src/log.c
int parse_logformat_string(const char *fmt, struct proxy *curproxy, struct lf_expr *lf_expr, int options, int cap, char **err);
Configuration example (HAProxy log-format directive):
log-format "host=%[req.hdr(host),lower] status=%[status]"
The %[...] wrapper tells parse_logformat_string() where each embedded sample
expression begins and ends. The text outside the brackets ("host=", " status=")
is emitted as-is.
1.3 Which one to use
..............................................................................
Use sample_parse_expr() when the configuration token is a single, standalone
sample expression (no surrounding text). This is the case for the otel filter
keywords such as "attribute", "event", "baggage", "status", "value", and
similar.
Use parse_logformat_string() when the value is a free-form string that may mix
literal text with zero or more embedded expressions.
2 Signal keywords
------------------------------------------------------------------------------
The OTel filter configuration uses one keyword per signal to create or update
signal-specific objects. The keyword names follow the OpenTelemetry
specification's own terminology rather than using informal synonyms.
Signal Keyword Creates / updates
-------- ----------- ------------------------------------------
Tracing span A trace span.
Metrics instrument A metric instrument (counter, gauge, ...).
Logging log-record A log record.
The tracing keyword follows the same logic. A "trace" is the complete
end-to-end path of a request through a distributed system, composed of one or
more "spans". Each span represents a single unit of work within that trace.
The configuration operates at the span level: it creates individual spans, sets
their parent-child relationships, and attaches attributes and events. Using
"trace" as the keyword would be imprecise because one does not configure a trace
directly; one configures the spans that collectively form a trace.
The metrics keyword is analogous. In the OpenTelemetry data model the
terminology is layered: a "metric" is the aggregated output that the SDK
produces after processing recorded measurements, while an "instrument" is the
concrete object through which those measurements are recorded -- a counter,
histogram, gauge, or up-down counter. The configuration operates at the
instrument level: it creates an instrument of a specific type and records values
through it. Using "metric" as the keyword would be imprecise because one does
not configure a metric directly; one configures an instrument that yields
metrics.
The logging keyword follows the same pattern. A "log" is the broad signal
category, while a "log record" is a single discrete entry within that signal.
The configuration operates at the log-record level: it creates individual log
records with a severity, a body, and optional attributes and span context.
Using "log" as the keyword would be imprecise because one does not configure a
log stream directly; one configures the individual log records that comprise it.

View file

@ -1,288 +0,0 @@
## HAProxy OpenTelemetry Filter (OTel)
The OTel filter enables HAProxy to emit telemetry data -- traces, metrics and
logs -- to any OpenTelemetry-compatible backend via the OpenTelemetry protocol
(OTLP).
It is the successor to the OpenTracing (OT) filter, built on the OpenTelemetry
standard which unifies distributed tracing, metrics and logging into a single
observability framework.
### Features
- **Distributed tracing** -- spans with parent-child relationships, context
propagation via HTTP headers or HAProxy variables, links, baggage and status.
- **Metrics** -- counter, histogram, up-down counter and gauge instruments with
configurable aggregation and bucket boundaries.
- **Logging** -- log records with severity levels, optional span correlation and
runtime-evaluated attributes.
- **Rate limiting** -- percentage-based sampling (0.0--100.0) for controlling
overhead.
- **ACL integration** -- fine-grained conditional execution at instrumentation,
scope and event levels.
- **CLI management** -- runtime enable/disable, rate adjustment, error mode
switching and status inspection.
- **Context propagation** -- inject/extract span contexts between cascaded
HAProxy instances or external services.
### Dependencies
The filter requires the
[OpenTelemetry C Wrapper](https://github.com/haproxytech/opentelemetry-c-wrapper)
library, which wraps the OpenTelemetry C++ SDK.
### Building
The OTel filter is compiled together with HAProxy by adding `USE_OTEL=1` to the
make command.
#### Using pkg-config
```
PKG_CONFIG_PATH=/opt/lib/pkgconfig make -j8 USE_OTEL=1 TARGET=linux-glibc
```
#### Explicit paths
```
make -j8 USE_OTEL=1 OTEL_INC=/opt/include OTEL_LIB=/opt/lib TARGET=linux-glibc
```
#### Build options
| Variable | Description |
|-----------------|-----------------------------------------------------|
| `USE_OTEL` | Enable the OpenTelemetry filter |
| `OTEL_DEBUG` | Compile in debug mode |
| `OTEL_INC` | Force path to opentelemetry-c-wrapper include files |
| `OTEL_LIB` | Force path to opentelemetry-c-wrapper library |
| `OTEL_RUNPATH` | Add opentelemetry-c-wrapper RUNPATH to executable |
| `OTEL_USE_VARS` | Enable context propagation via HAProxy variables |
#### Debug mode
```
PKG_CONFIG_PATH=/opt/lib/pkgconfig make -j8 USE_OTEL=1 OTEL_DEBUG=1 TARGET=linux-glibc
```
#### Variable-based context propagation
```
PKG_CONFIG_PATH=/opt/lib/pkgconfig make -j8 USE_OTEL=1 OTEL_USE_VARS=1 TARGET=linux-glibc
```
#### Verifying the build
```
./haproxy -vv | grep -i opentelemetry
```
If the filter is built in, the output contains:
```
Built with OpenTelemetry support (C++ version 1.26.0, C Wrapper version 1.0.0-842).
[OTEL] opentelemetry
```
#### Library path at runtime
When pkg-config is not used, the executable may not find the library at startup.
Use `LD_LIBRARY_PATH` or build with `OTEL_RUNPATH=1`:
```
LD_LIBRARY_PATH=/opt/lib ./haproxy ...
```
```
make -j8 USE_OTEL=1 OTEL_RUNPATH=1 OTEL_INC=/opt/include OTEL_LIB=/opt/lib TARGET=linux-glibc
```
### Configuration
The filter uses a two-file configuration model:
1. **OTel configuration file** (`.cfg`) -- defines the telemetry model:
instrumentation settings, scopes and groups.
2. **YAML configuration file** (`.yml`) -- defines the OpenTelemetry SDK
pipeline: exporters, samplers, processors, providers and signal routing.
#### Activating the filter
The OTel filter requires the `insecure-fork-wanted` keyword in the HAProxy
`global` section. This is necessary because the OpenTelemetry C++ SDK creates
background threads for data export and batch processing. HAProxy will refuse
to load the configuration if this keyword is missing.
```
global
insecure-fork-wanted
...
```
Add the filter to a HAProxy proxy section (frontend/listen/backend):
```
frontend my-frontend
...
filter opentelemetry [id <id>] config <file>
...
```
If no filter id is specified, `otel-filter` is used as default.
#### OTel configuration file structure
The OTel configuration file contains three section types:
- `otel-instrumentation` -- mandatory; references the YAML file, sets rate
limits, error modes, logging and declares groups and scopes.
- `otel-scope` -- defines actions (spans, attributes, metrics, logs) triggered
by stream events or from groups.
- `otel-group` -- a named collection of scopes triggered from HAProxy TCP/HTTP
rules.
#### Minimal YAML configuration
```yaml
exporters:
my_exporter:
type: otlp_http
endpoint: "http://localhost:4318/v1/traces"
samplers:
my_sampler:
type: always_on
processors:
my_processor:
type: batch
providers:
my_provider:
resources:
- service.name: "haproxy"
signals:
traces:
scope_name: "HAProxy OTel"
exporters: my_exporter
samplers: my_sampler
processors: my_processor
providers: my_provider
```
#### Supported YAML exporters
| Type | Description |
|-----------------|---------------------------------------|
| `otlp_grpc` | OTLP over gRPC |
| `otlp_http` | OTLP over HTTP (JSON or Protobuf) |
| `otlp_file` | Local files in OTLP format |
| `zipkin` | Zipkin-compatible backends |
| `elasticsearch` | Elasticsearch |
| `ostream` | Text output to a file (for debugging) |
| `memory` | In-memory buffer (for testing) |
### Scope keywords
| Keyword | Description |
|----------------|---------------------------------------------------------|
| `span` | Create or reference a span |
| `attribute` | Set key-value span attributes |
| `event` | Add timestamped span events |
| `baggage` | Set context propagation data |
| `status` | Set span status (ok/error/ignore/unset) |
| `link` | Add span links to related spans |
| `inject` | Inject context into headers or variables |
| `extract` | Extract context from headers or variables |
| `finish` | Close spans (supports wildcards: `*`, `*req*`, `*res*`) |
| `instrument` | Create or update metric instruments |
| `log-record` | Emit a log record with severity |
| `otel-event` | Bind scope to a filter event with optional ACL |
| `idle-timeout` | Set periodic event interval for idle streams |
### CLI commands
Available via the HAProxy CLI socket (prefix: `flt-otel`):
| Command | Description |
|----------------------------|------------------------------------|
| `flt-otel status` | Show filter status |
| `flt-otel enable` | Enable the filter |
| `flt-otel disable` | Disable the filter |
| `flt-otel hard-errors` | Enable hard-errors mode |
| `flt-otel soft-errors` | Disable hard-errors mode |
| `flt-otel logging [state]` | Set logging state |
| `flt-otel rate [value]` | Set or show the rate limit |
| `flt-otel debug [level]` | Set debug level (debug build only) |
When invoked without arguments, `rate`, `logging` and `debug` display the
current value.
### Performance
Benchmark results from the standalone (`sa`) configuration, which exercises all
events (worst-case scenario):
| Rate limit | Req/s | Avg latency | Overhead |
|------------|--------|-------------|----------|
| 100.0% | 38,202 | 213.08 us | 21.6% |
| 50.0% | 42,777 | 190.49 us | 12.2% |
| 25.0% | 45,302 | 180.46 us | 7.0% |
| 10.0% | 46,879 | 174.69 us | 3.7% |
| 2.5% | 47,993 | 170.58 us | 1.4% |
| disabled | 48,788 | 167.74 us | ~0 |
| off | 48,697 | 168.00 us | baseline |
With a rate limit of 10% or less, the performance impact is negligible.
Detailed methodology and additional results are in the `test/` directory.
### Test configurations
The `test/` directory contains ready-to-run example configurations:
- **sa** -- standalone; the most comprehensive example, demonstrating spans,
attributes, events, links, baggage, status, metrics, log records, ACL
conditions and idle-timeout events.
- **fe/be** -- distributed tracing across two cascaded HAProxy instances using
HTTP header-based context propagation.
- **ctx** -- context propagation via HAProxy variables using the inject/extract
mechanism.
- **cmp** -- minimal configuration for benchmarking comparison.
- **empty** -- filter initialized with no active telemetry.
#### Quick start with Jaeger
Start a Jaeger all-in-one container:
```
docker run -d --name jaeger -p 4317:4317 -p 4318:4318 -p 16686:16686 jaegertracing/all-in-one:latest
```
Run one of the test configurations:
```
./test/run-sa.sh
```
Open the Jaeger UI at `http://localhost:16686` to view traces.
### Documentation
Detailed documentation is available in the following files:
- [README](README) -- complete reference documentation
- [README-configuration](README-configuration) -- configuration guide
- [README-conf](README-conf) -- configuration details
- [README-design](README-design) -- cross-cutting design patterns
- [README-implementation](README-implementation) -- component architecture
- [README-func](README-func) -- function reference
- [README-misc](README-misc) -- miscellaneous notes
### Copyright
Copyright 2026 HAProxy Technologies
### Author
Miroslav Zagorac <mzagorac@haproxy.com>

View file

@ -1,28 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_CLI_H_
#define _OTEL_CLI_H_
#define FLT_OTEL_CLI_CMD "flt-otel"
#define FLT_OTEL_CLI_LOGGING_OFF "off"
#define FLT_OTEL_CLI_LOGGING_ON "on"
#define FLT_OTEL_CLI_LOGGING_NOLOGNORM "dontlog-normal"
#define FLT_OTEL_CLI_LOGGING_STATE(a) (((a) & FLT_OTEL_LOGGING_ON) ? (((a) & FLT_OTEL_LOGGING_NOLOGNORM) ? "enabled, " FLT_OTEL_CLI_LOGGING_NOLOGNORM : "enabled") : "disabled")
#define FLT_OTEL_CLI_MSG_CAT(a) (((a) == NULL) ? "" : (a)), (((a) == NULL) ? "" : "\n")
/* Register CLI keywords for the OTel filter. */
void flt_otel_cli_init(void);
#endif /* _OTEL_CLI_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,313 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_CONF_H_
#define _OTEL_CONF_H_
/* Extract the OTel filter configuration from a filter instance. */
#define FLT_OTEL_CONF(f) ((struct flt_otel_conf *)FLT_CONF(f))
/* Expand to a string pointer and its length for a named member. */
#define FLT_OTEL_STR_HDR_ARGS(p,m) (p)->m, (p)->m##_len
/***
* It should be noted that the macro FLT_OTEL_CONF_HDR_ARGS() does not have
* all the parameters defined that would correspond to the format found in
* the FLT_OTEL_CONF_HDR_FMT macro (first pointer is missing).
*
* This is because during the expansion of the OTELC_DBG_STRUCT() macro, an
* incorrect conversion is performed and instead of the first correct code,
* a second incorrect code is generated:
*
* do {
* if ((p) == NULL)
* ..
* } while (0)
*
* do {
* if ((p), (int) (p)->id_len, (p)->id, (p)->id_len, (p)->cfg_line == NULL)
* ..
* } while (0)
*
*/
#define FLT_OTEL_CONF_HDR_FMT "%p:{ { '%.*s' %zu %d } "
#define FLT_OTEL_CONF_HDR_ARGS(p,m) (int)(p)->m##_len, (p)->m, (p)->m##_len, (p)->cfg_line
/*
* Special two-byte prefix that triggers automatic id generation in
* FLT_OTEL_CONF_FUNC_INIT(): the text after the prefix is combined
* with the configuration line number to form a unique identifier.
*/
#define FLT_OTEL_CONF_HDR_SPECIAL "\x1e\x1f"
#define FLT_OTEL_CONF_STR_CMP(s,S) ((s##_len == S##_len) && (memcmp(s, S, S##_len) == 0))
#define FLT_OTEL_DBG_CONF_SAMPLE_EXPR(h,p) \
OTELC_DBG(DEBUG, h "%p:{ '%s' %p }", (p), (p)->fmt_expr, (p)->expr)
#define FLT_OTEL_DBG_CONF_SAMPLE(h,p) \
OTELC_DBG(DEBUG, h "%p:{ '%s' '%s' %s %s %d %p %hhu }", (p), \
(p)->key, (p)->fmt_string, otelc_value_dump(&((p)->extra), ""), \
flt_otel_list_dump(&((p)->exprs)), (p)->num_exprs, &((p)->lf_expr), (p)->lf_used)
#define FLT_OTEL_DBG_CONF_HDR(h,p,i) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "}", (p), FLT_OTEL_CONF_HDR_ARGS(p, i))
#define FLT_OTEL_DBG_CONF_CONTEXT(h,p) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "0x%02hhx }", (p), FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->flags)
#define FLT_OTEL_DBG_CONF_SPAN(h,p) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %zu %s' %zu %hhu 0x%02hhx %s %s %s %s %s }", \
(p), FLT_OTEL_CONF_HDR_ARGS(p, id), FLT_OTEL_STR_HDR_ARGS(p, ref_id), \
FLT_OTEL_STR_HDR_ARGS(p, ctx_id), (p)->flag_root, (p)->ctx_flags, \
flt_otel_list_dump(&((p)->links)), flt_otel_list_dump(&((p)->attributes)), \
flt_otel_list_dump(&((p)->events)), flt_otel_list_dump(&((p)->baggages)), \
flt_otel_list_dump(&((p)->statuses)))
#define FLT_OTEL_DBG_CONF_SCOPE(h,p) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %d %u %s %p %s %s %s %s %s }", (p), \
FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->flag_used, (p)->event, (p)->idle_timeout, \
flt_otel_list_dump(&((p)->acls)), (p)->cond, flt_otel_list_dump(&((p)->contexts)), \
flt_otel_list_dump(&((p)->spans)), flt_otel_list_dump(&((p)->spans_to_finish)), \
flt_otel_list_dump(&((p)->instruments)), flt_otel_list_dump(&((p)->log_records)))
#define FLT_OTEL_DBG_CONF_GROUP(h,p) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %s }", (p), \
FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->flag_used, flt_otel_list_dump(&((p)->ph_scopes)))
#define FLT_OTEL_DBG_CONF_PH(h,p) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%p }", (p), FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->ptr)
#define FLT_OTEL_DBG_CONF_INSTR(h,p) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %p %p %p %u %hhu %hhu 0x%02hhx %p:%s 0x%08x %u %s %s %s }", \
(p), FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->config, (p)->tracer, (p)->meter, (p)->logger, \
(p)->rate_limit, (p)->flag_harderr, (p)->flag_disabled, (p)->logging, &((p)->proxy_log), \
flt_otel_list_dump(&((p)->proxy_log.loggers)), (p)->analyzers, (p)->idle_timeout, \
flt_otel_list_dump(&((p)->acls)), flt_otel_list_dump(&((p)->ph_groups)), \
flt_otel_list_dump(&((p)->ph_scopes)))
#define FLT_OTEL_DBG_CONF_INSTRUMENT(h,p) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%" PRId64 " %d %d '%s' '%s' %s %s %p %zu %p }", (p), \
FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->idx, (p)->type, (p)->aggr_type, OTELC_STR_ARG((p)->description), \
OTELC_STR_ARG((p)->unit), flt_otel_list_dump(&((p)->samples)), flt_otel_list_dump(&((p)->attributes)), \
(p)->ref, (p)->bounds_num, (p)->bounds)
#define FLT_OTEL_DBG_CONF_LOG_RECORD(h,p) \
OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%d %" PRId64 " '%s' '%s' %s %s }", (p), \
FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->severity, (p)->event_id, OTELC_STR_ARG((p)->event_name), \
OTELC_STR_ARG((p)->span), flt_otel_list_dump(&((p)->attributes)), flt_otel_list_dump(&((p)->samples)))
#define FLT_OTEL_DBG_CONF(h,p) \
OTELC_DBG(DEBUG, h "%p:{ %p '%s' '%s' %p %s %s }", (p), \
(p)->proxy, (p)->id, (p)->cfg_file, (p)->instr, \
flt_otel_list_dump(&((p)->groups)), flt_otel_list_dump(&((p)->scopes)))
/* Anonymous struct containing a string pointer and its length. */
#define FLT_OTEL_CONF_STR(p) \
struct { \
char *p; \
size_t p##_len; \
}
/* Common header embedded in all configuration structures. */
#define FLT_OTEL_CONF_HDR(p) \
struct { \
FLT_OTEL_CONF_STR(p); \
int cfg_line; \
struct list list; \
}
/* Generic configuration header used for simple named list entries. */
struct flt_otel_conf_hdr {
FLT_OTEL_CONF_HDR(id); /* A list containing header names. */
};
/* flt_otel_conf_sample->exprs */
struct flt_otel_conf_sample_expr {
FLT_OTEL_CONF_HDR(fmt_expr); /* The original sample expression format string. */
struct sample_expr *expr; /* The sample expression. */
};
/*
* flt_otel_conf_span->attributes
* flt_otel_conf_span->events (event_name -> OTELC_VALUE_STR(&extra))
* flt_otel_conf_span->baggages
* flt_otel_conf_span->statuses (status_code -> extra.u.value_int32)
* flt_otel_conf_instrument->samples
* flt_otel_conf_log_record->samples
*/
struct flt_otel_conf_sample {
FLT_OTEL_CONF_HDR(key); /* The list containing sample names. */
char *fmt_string; /* All sample-expression arguments are combined into a single string. */
struct otelc_value extra; /* Optional supplementary data. */
struct list exprs; /* Used to chain sample expressions. */
int num_exprs; /* Number of defined expressions. */
struct lf_expr lf_expr; /* The log-format expression. */
bool lf_used; /* Whether lf_expr is used instead of exprs. */
};
/*
* flt_otel_conf_scope->spans_to_finish
*
* It can be seen that this structure is actually identical to the structure
* flt_otel_conf_hdr.
*/
struct flt_otel_conf_str {
FLT_OTEL_CONF_HDR(str); /* A list containing character strings. */
};
/* flt_otel_conf_scope->contexts */
struct flt_otel_conf_context {
FLT_OTEL_CONF_HDR(id); /* The name of the context. */
uint8_t flags; /* The type of storage from which the span context is extracted. */
};
/* flt_otel_conf_span->links */
struct flt_otel_conf_link {
FLT_OTEL_CONF_HDR(span); /* The list containing link names. */
};
/*
* Span configuration within a scope.
* flt_otel_conf_scope->spans
*/
struct flt_otel_conf_span {
FLT_OTEL_CONF_HDR(id); /* The name of the span. */
FLT_OTEL_CONF_STR(ref_id); /* The reference name, if used. */
FLT_OTEL_CONF_STR(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 links; /* The set of linked span names. */
struct list attributes; /* The set of key:value attributes. */
struct list events; /* The set of events with key-value attributes. */
struct list baggages; /* The set of key:value baggage items. */
struct list statuses; /* Span status code and description (only one per list). */
};
/*
* Metric instrument configuration within a scope.
* flt_otel_conf_scope->instruments
*/
struct flt_otel_conf_instrument {
FLT_OTEL_CONF_HDR(id); /* The name of the instrument. */
int64_t idx; /* Meter instrument index (-1 if not yet created). */
otelc_metric_instrument_t type; /* Instrument type (or UPDATE). */
otelc_metric_aggregation_type_t aggr_type; /* Aggregation type for the view (create only). */
char *description; /* Instrument description (create only). */
char *unit; /* Instrument unit (create only). */
struct list samples; /* Sample expressions for the value. */
double *bounds; /* Histogram bucket boundaries (create only). */
size_t bounds_num; /* Number of histogram bucket boundaries. */
struct list attributes; /* Instrument attributes (update only, flt_otel_conf_sample). */
struct flt_otel_conf_instrument *ref; /* Resolved create-form instrument (update only). */
};
/*
* Log record configuration within a scope.
* flt_otel_conf_scope->log_records
*/
struct flt_otel_conf_log_record {
FLT_OTEL_CONF_HDR(id); /* Required by macro; member <id> is not used directly. */
otelc_log_severity_t severity; /* The severity level. */
int64_t event_id; /* Optional event identifier. */
char *event_name; /* Optional event name. */
char *span; /* Optional span reference. */
struct list attributes; /* Log record attributes (flt_otel_conf_sample). */
struct list samples; /* Sample expressions for the body. */
};
/* Configuration for a single event scope. */
struct flt_otel_conf_scope {
FLT_OTEL_CONF_HDR(id); /* The scope name. */
bool flag_used; /* The indication that the scope is being used. */
int event; /* FLT_OTEL_EVENT_* */
uint idle_timeout; /* Idle timeout interval in milliseconds (0 = off). */
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 spans_to_finish; /* The list of spans scheduled for finishing. */
struct list instruments; /* The list of metric instruments. */
struct list log_records; /* The list of log records. */
};
/* Configuration for a named group of scopes. */
struct flt_otel_conf_group {
FLT_OTEL_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. */
};
/* Placeholder referencing a scope or group by name. */
struct flt_otel_conf_ph {
FLT_OTEL_CONF_HDR(id); /* The scope/group name. */
void *ptr; /* Pointer to real placeholder structure. */
};
#define flt_otel_conf_ph_group flt_otel_conf_ph
#define flt_otel_conf_ph_scope flt_otel_conf_ph
/* Top-level OTel instrumentation settings (tracer, meter, options). */
struct flt_otel_conf_instr {
FLT_OTEL_CONF_HDR(id); /* The OpenTelemetry instrumentation name. */
char *config; /* The OpenTelemetry configuration file name. */
struct otelc_tracer *tracer; /* The OpenTelemetry tracer handle. */
struct otelc_meter *meter; /* The OpenTelemetry meter handle. */
struct otelc_logger *logger; /* The OpenTelemetry logger 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. */
uint idle_timeout; /* Minimum idle timeout across scopes (ms, 0 = off). */
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. */
};
/* Runtime counters for filter diagnostics. */
struct flt_otel_counters {
#ifdef DEBUG_OTEL
struct {
bool flag_used; /* Whether this event is used. */
uint64_t htx[2]; /* htx_is_empty() function result counter. */
} event[FLT_OTEL_EVENT_MAX];
#endif
#ifdef FLT_OTEL_USE_COUNTERS
uint64_t attached[4]; /* [run rate-limit disabled error] */
uint64_t disabled[2]; /* How many times stream processing is disabled. */
#endif
};
/* The OpenTelemetry filter configuration. */
struct flt_otel_conf {
struct proxy *proxy; /* Proxy owning the filter. */
char *id; /* The OpenTelemetry filter id. */
char *cfg_file; /* The OpenTelemetry filter configuration file name. */
struct flt_otel_conf_instr *instr; /* The OpenTelemetry instrumentation settings. */
struct list groups; /* List of all available groups. */
struct list scopes; /* List of all available scopes. */
struct flt_otel_counters cnt; /* Various counters related to filter operation. */
struct list smp_args; /* Deferred OTEL sample fetch args to resolve. */
};
/* Allocate and initialize a sample from parsed arguments. */
struct flt_otel_conf_sample *flt_otel_conf_sample_init_ex(const char **args, int idx, int n, const struct otelc_value *extra, int line, struct list *head, char **err);
/* Allocate and initialize the top-level OTel filter configuration. */
struct flt_otel_conf *flt_otel_conf_init(struct proxy *px);
/* Free the top-level OTel filter configuration. */
void flt_otel_conf_free(struct flt_otel_conf **ptr);
#endif /* _OTEL_CONF_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,128 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_CONF_FUNCS_H_
#define _OTEL_CONF_FUNCS_H_
/*
* Macro that generates a flt_otel_conf_<type>_init() function. The generated
* function allocates and initializes a configuration structure of the given
* type, checks for duplicate names in the list, and optionally runs a custom
* initializer body.
*/
#define FLT_OTEL_CONF_FUNC_INIT(_type_, _id_, _func_) \
struct flt_otel_conf_##_type_ *flt_otel_conf_##_type_##_init(const char *id, int line, struct list *head, char **err) \
{ \
struct flt_otel_conf_##_type_ *retptr = NULL; \
struct flt_otel_conf_##_type_ *ptr; \
char id_buffer[FLT_OTEL_ID_MAXLEN + 16]; \
size_t _id_##_len; \
\
OTELC_FUNC("\"%s\", %d, %p, %p:%p", OTELC_STR_ARG(id), line, head, OTELC_DPTR_ARGS(err)); \
\
if ((id == NULL) || (*id == '\0')) { \
FLT_OTEL_ERR("name not set"); \
\
OTELC_RETURN_PTR(retptr); \
} \
else if ((id[0] == FLT_OTEL_CONF_HDR_SPECIAL[0]) && (id[1] == FLT_OTEL_CONF_HDR_SPECIAL[1])) { \
(void)snprintf(id_buffer, sizeof(id_buffer), "%s:%d", id + 2, line); \
\
id = id_buffer; \
} \
\
_id_##_len = strlen(id); \
if (_id_##_len >= FLT_OTEL_ID_MAXLEN) { \
FLT_OTEL_ERR("'%s' : name too long", id); \
\
OTELC_RETURN_PTR(retptr); \
} \
\
if (head != NULL) \
list_for_each_entry(ptr, head, list) \
if (strcmp(ptr->_id_, id) == 0) { \
FLT_OTEL_ERR("'%s' : already defined", id); \
\
OTELC_RETURN_PTR(retptr); \
} \
\
retptr = OTELC_CALLOC(1, sizeof(*retptr)); \
if (retptr != NULL) { \
retptr->cfg_line = line; \
retptr->_id_##_len = _id_##_len; \
retptr->_id_ = OTELC_STRDUP(id); \
if (retptr->_id_ != NULL) { \
if (head != NULL) \
LIST_APPEND(head, &(retptr->list)); \
\
FLT_OTEL_DBG_CONF_HDR("- conf_" #_type_ " init ", retptr, _id_); \
} \
else \
OTELC_SFREE_CLEAR(retptr); \
} \
\
if (retptr != NULL) { \
_func_ \
} \
\
if (retptr == NULL) \
FLT_OTEL_ERR("out of memory"); \
\
OTELC_RETURN_PTR(retptr); \
}
/*
* Macro that generates a flt_otel_conf_<type>_free() function. The generated
* function runs a custom cleanup body, then frees the name string, removes the
* structure from its list, and frees the structure.
*/
#define FLT_OTEL_CONF_FUNC_FREE(_type_, _id_, _func_) \
void flt_otel_conf_##_type_##_free(struct flt_otel_conf_##_type_ **ptr) \
{ \
OTELC_FUNC("%p:%p", OTELC_DPTR_ARGS(ptr)); \
\
if ((ptr == NULL) || (*ptr == NULL)) \
OTELC_RETURN(); \
\
{ _func_ } \
\
OTELC_SFREE((*ptr)->_id_); \
FLT_OTEL_LIST_DEL(&((*ptr)->list)); \
OTELC_SFREE_CLEAR(*ptr); \
\
OTELC_RETURN(); \
}
/* The FLT_OTEL_LIST_DESTROY() macro uses the following two definitions. */
#define flt_otel_conf_ph_group_free flt_otel_conf_ph_free
#define flt_otel_conf_ph_scope_free flt_otel_conf_ph_free
/* Declare init/free function prototypes for a configuration type. */
#define FLT_OTEL_CONF_FUNC_DECL(_type_) \
struct flt_otel_conf_##_type_ *flt_otel_conf_##_type_##_init(const char *id, int line, struct list *head, char **err); \
void flt_otel_conf_##_type_##_free(struct flt_otel_conf_##_type_ **ptr);
FLT_OTEL_CONF_FUNC_DECL(hdr)
FLT_OTEL_CONF_FUNC_DECL(str)
FLT_OTEL_CONF_FUNC_DECL(ph)
FLT_OTEL_CONF_FUNC_DECL(sample_expr)
FLT_OTEL_CONF_FUNC_DECL(sample)
FLT_OTEL_CONF_FUNC_DECL(link)
FLT_OTEL_CONF_FUNC_DECL(context)
FLT_OTEL_CONF_FUNC_DECL(span)
FLT_OTEL_CONF_FUNC_DECL(scope)
FLT_OTEL_CONF_FUNC_DECL(instrument)
FLT_OTEL_CONF_FUNC_DECL(log_record)
FLT_OTEL_CONF_FUNC_DECL(group)
FLT_OTEL_CONF_FUNC_DECL(instr)
#endif /* _OTEL_CONF_FUNCS_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,34 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_CONFIG_H_
#define _OTEL_CONFIG_H_
/* Memory pool selection flags. */
#define USE_POOL_BUFFER
#define USE_POOL_OTEL_SPAN_CONTEXT
#define USE_POOL_OTEL_SCOPE_SPAN
#define USE_POOL_OTEL_SCOPE_CONTEXT
#define USE_POOL_OTEL_RUNTIME_CONTEXT
#define USE_TRASH_CHUNK
/* Enable per-event and per-stream diagnostic counters in debug builds. */
#if defined(DEBUG_OTEL) && !defined(FLT_OTEL_USE_COUNTERS)
# define FLT_OTEL_USE_COUNTERS
#endif
#define FLT_OTEL_ID_MAXLEN 64 /* Maximum identifier length. */
#define FLT_OTEL_DEBUG_LEVEL 0b11101111111 /* Default debug bitmask. */
#define FLT_OTEL_ATTR_INIT_SIZE 8 /* Initial attribute array capacity. */
#define FLT_OTEL_ATTR_INC_SIZE 4 /* Attribute array growth increment. */
#endif /* _OTEL_CONFIG_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,55 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_DEBUG_H_
#define _OTEL_DEBUG_H_
#ifdef DEBUG_FULL
# define DEBUG_OTEL
#endif
/*
* FLT_OTEL_DBG_ARGS - include extra debug-only function parameters.
* FLT_OTEL_DBG_BUF - dump a buffer structure for debugging.
*
* When DEBUG_OTEL is not defined, these expand to nothing.
*/
#ifdef DEBUG_OTEL
# define FLT_OTEL_DBG_ARGS(a, ...) a, ##__VA_ARGS__
# define FLT_OTEL_DBG_BUF(l,a) OTELC_DBG(l, "%p:{ %zu %p %zu %zu }", (a), (a)->size, (a)->area, (a)->data, (a)->head)
#else
# define FLT_OTEL_DBG_ARGS(...)
# define FLT_OTEL_DBG_BUF(...) while (0)
#endif /* DEBUG_OTEL */
/*
* ON | NOLOGNORM |
* -----+-----------+-------------
* 0 | 0 | no log
* 0 | 1 | no log
* 1 | 0 | log all
* 1 | 1 | log errors
* -----+-----------+-------------
*/
#define FLT_OTEL_LOG(l,f, ...) \
do { \
if (!(conf->instr->logging & FLT_OTEL_LOGGING_ON)) \
OTELC_DBG(DEBUG, "NOLOG[%d]: [" FLT_OTEL_SCOPE "]: [%s] " f, (l), conf->id, ##__VA_ARGS__); \
else if ((conf->instr->logging & FLT_OTEL_LOGGING_NOLOGNORM) && ((l) > LOG_ERR)) \
OTELC_DBG(NOTICE, "NOLOG[%d]: [" FLT_OTEL_SCOPE "]: [%s] " f, (l), conf->id, ##__VA_ARGS__); \
else { \
send_log(&(conf->instr->proxy_log), (l), "[" FLT_OTEL_SCOPE "]: [%s] " f "\n", conf->id, ##__VA_ARGS__); \
\
OTELC_DBG(INFO, "LOG[%d]: %s", (l), logline); \
} \
} while (0)
#endif /* _OTEL_DEBUG_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,98 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_DEFINE_H_
#define _OTEL_DEFINE_H_
/* Safe pointer dereference with default value. */
#define FLT_OTEL_DEREF(p,m,v) (((p) != NULL) ? (p)->m : (v))
/* Check whether argument at index n is in range, non-NULL and non-empty. */
#define FLT_OTEL_ARG_ISVALID(n) ({ typeof(n) _n = (n); OTELC_IN_RANGE(_n, 0, MAX_LINE_ARGS - 1) && (args[_n] != NULL) && (*args[_n] != '\0'); })
/* Convert a uint32_t rate value to a floating-point percentage. */
#define FLT_OTEL_U32_FLOAT(a) ((a) * 100.0 / UINT32_MAX)
/* Convert a floating-point percentage to a uint32_t rate value. */
#define FLT_OTEL_FLOAT_U32(a) ((uint32_t)((a) / 100.0 * UINT32_MAX + 0.5))
#define FLT_OTEL_STR_DASH_72 "------------------------------------------------------------------------"
#define FLT_OTEL_STR_DASH_78 FLT_OTEL_STR_DASH_72 "------"
#define FLT_OTEL_STR_FLAG_YN(a) ((a) ? "yes" : "no")
/* Compile-time string length excluding the null terminator. */
#define FLT_OTEL_STR_SIZE(a) (sizeof(a) - 1)
/* Expand to address and length pair for a string literal. */
#define FLT_OTEL_STR_ADDRSIZE(a) (a), FLT_OTEL_STR_SIZE(a)
/* Compare a runtime string against a compile-time string literal. */
#define FLT_OTEL_STR_CMP(S,s) ((s##_len == FLT_OTEL_STR_SIZE(S)) && (memcmp((s), FLT_OTEL_STR_ADDRSIZE(S)) == 0))
/* Tolerance for double comparison in flt_otel_qsort_compar_double(). */
#define FLT_OTEL_DBL_EPSILON 1e-9
/* Execute a statement exactly once across all invocations. */
#define FLT_OTEL_RUN_ONCE(f) do { static bool _f = 1; if (_f) { _f = 0; { f; } } } while (0)
/* Check whether a list head has been initialized. */
#define FLT_OTEL_LIST_ISVALID(a) ({ typeof(a) _a = (a); (_a != NULL) && (_a->n != NULL) && (_a->p != NULL); })
/* Safely delete a list element if its list head is valid. */
#define FLT_OTEL_LIST_DEL(a) do { if (FLT_OTEL_LIST_ISVALID(a)) LIST_DELETE(a); } while (0)
/* Destroy all elements in a typed configuration list. */
#define FLT_OTEL_LIST_DESTROY(t,h) \
do { \
struct flt_otel_conf_##t *_ptr, *_back; \
\
if (!FLT_OTEL_LIST_ISVALID(h) || LIST_ISEMPTY(h)) \
break; \
\
OTELC_DBG(NOTICE, "- deleting " #t " list %s", flt_otel_list_dump(h)); \
\
list_for_each_entry_safe(_ptr, _back, (h), list) \
flt_otel_conf_##t##_free(&_ptr); \
} while (0)
/* Declare a rotating thread-local string buffer pool. */
#define FLT_OTEL_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)
/* Format an error message if none has been set yet. */
#define FLT_OTEL_ERR(f, ...) \
do { \
if ((err != NULL) && (*err == NULL)) { \
(void)memprintf(err, f, ##__VA_ARGS__); \
\
OTELC_DBG(DEBUG, "err: '%s'", *err); \
} \
} while (0)
/* Append to an existing error message unconditionally. */
#define FLT_OTEL_ERR_APPEND(f, ...) \
do { \
if (err != NULL) \
(void)memprintf(err, f, ##__VA_ARGS__); \
} while (0)
/* Log an error message and free its memory. */
#define FLT_OTEL_ERR_FREE(p) \
do { \
if ((p) == NULL) \
break; \
\
OTELC_DBG(LOG, "%s:%d: ERROR: %s", __func__, __LINE__, (p)); \
OTELC_SFREE_CLEAR(p); \
} while (0)
#endif /* _OTEL_DEFINE_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,152 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_EVENT_H_
#define _OTEL_EVENT_H_
/*
* This must be defined in order for macro FLT_OTEL_EVENT_DEFINES
* and structure flt_otel_event_data to have the correct contents.
*/
#define AN__NONE 0
#define AN__STREAM_START 0 /* on-stream-start */
#define AN__STREAM_STOP 0 /* on-stream-stop */
#define AN__IDLE_TIMEOUT 0 /* on-idle-timeout */
#define AN__BACKEND_SET 0 /* on-backend-set */
#define AN_REQ_HTTP_HEADERS 0 /* on-http-headers-request */
#define AN_RES_HTTP_HEADERS 0 /* on-http-headers-response */
#define AN_REQ_HTTP_END 0 /* on-http-end-request */
#define AN_RES_HTTP_END 0 /* on-http-end-response */
#define AN_RES_HTTP_REPLY 0 /* on-http-reply */
#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
#define SMP_OPT_DIR_ 0xff
/*
* 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_OTEL_EVENT_NONE is used as an index for 'otel-scope' sections that do not
* have an event defined. The 'otel-scope' sections thus defined can be used
* within the 'otel-group' section.
*
* A description of the macro arguments can be found in the structure
* flt_otel_event_data definition.
*
* The following table is derived from the definitions AN_REQ_* and AN_RES_*
* found in the HAProxy include file include/haproxy/channel-t.h.
*/
#define FLT_OTEL_EVENT_DEFINES \
/* Stream lifecycle pseudo-events (an_bit = 0, not tied to a channel analyzer) */ \
FLT_OTEL_EVENT_DEF( NONE, , , , 0, "") \
FLT_OTEL_EVENT_DEF( STREAM_START, , , , 0, "on-stream-start") \
FLT_OTEL_EVENT_DEF( STREAM_STOP, , , , 0, "on-stream-stop") \
FLT_OTEL_EVENT_DEF( CLIENT_SESS_START, REQ, CON_ACC, , 1, "on-client-session-start") \
FLT_OTEL_EVENT_DEF( IDLE_TIMEOUT, , , , 0, "on-idle-timeout") \
FLT_OTEL_EVENT_DEF( BACKEND_SET, , , , 0, "on-backend-set") \
\
/* Request analyzers */ \
/* FLT_OTEL_EVENT_DEF( FLT_START_FE, REQ, , , , "on-filter-start") */ \
FLT_OTEL_EVENT_DEF( INSPECT_FE, REQ, REQ_CNT, , 1, "on-frontend-tcp-request") \
FLT_OTEL_EVENT_DEF( WAIT_HTTP, REQ, , , 1, "on-http-wait-request") \
FLT_OTEL_EVENT_DEF( HTTP_BODY, REQ, , , 1, "on-http-body-request") \
FLT_OTEL_EVENT_DEF( HTTP_PROCESS_FE, REQ, HRQ_HDR, , 1, "on-frontend-http-request") \
FLT_OTEL_EVENT_DEF( SWITCHING_RULES, REQ, , , 1, "on-switching-rules-request") \
/* FLT_OTEL_EVENT_DEF( FLT_START_BE, REQ, , , , "") */ \
FLT_OTEL_EVENT_DEF( INSPECT_BE, REQ, REQ_CNT, REQ_CNT, 1, "on-backend-tcp-request") \
FLT_OTEL_EVENT_DEF( HTTP_PROCESS_BE, REQ, HRQ_HDR, HRQ_HDR, 1, "on-backend-http-request") \
/* FLT_OTEL_EVENT_DEF( HTTP_TARPIT, REQ, , , 1, "on-http-tarpit-request") */ \
FLT_OTEL_EVENT_DEF( SRV_RULES, REQ, , , 1, "on-process-server-rules-request") \
FLT_OTEL_EVENT_DEF( HTTP_INNER, REQ, , , 1, "on-http-process-request") \
FLT_OTEL_EVENT_DEF( PRST_RDP_COOKIE, REQ, , , 1, "on-tcp-rdp-cookie-request") \
FLT_OTEL_EVENT_DEF( STICKING_RULES, REQ, , , 1, "on-process-sticking-rules-request") \
/* FLT_OTEL_EVENT_DEF( FLT_HTTP_HDRS, REQ, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( HTTP_XFER_BODY, REQ, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( WAIT_CLI, REQ, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( FLT_XFER_DATA, REQ, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( FLT_END, REQ, , , , "") */ \
FLT_OTEL_EVENT_DEF( HTTP_HEADERS, REQ, HRQ_HDR, HRQ_HDR, 1, "on-http-headers-request") \
FLT_OTEL_EVENT_DEF( HTTP_END, REQ, , , 1, "on-http-end-request") \
FLT_OTEL_EVENT_DEF( CLIENT_SESS_END, REQ, , , 0, "on-client-session-end") \
FLT_OTEL_EVENT_DEF(SERVER_UNAVAILABLE, REQ, , , 0, "on-server-unavailable") \
\
/* Response analyzers */ \
FLT_OTEL_EVENT_DEF( SERVER_SESS_START, RES, , SRV_CON, 0, "on-server-session-start") \
/* FLT_OTEL_EVENT_DEF( FLT_START_FE, RES, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( FLT_START_BE, RES, , , , "") */ \
FLT_OTEL_EVENT_DEF( INSPECT, RES, RES_CNT, RES_CNT, 0, "on-tcp-response") \
FLT_OTEL_EVENT_DEF( WAIT_HTTP, RES, , , 1, "on-http-wait-response") \
FLT_OTEL_EVENT_DEF( STORE_RULES, RES, , , 1, "on-process-store-rules-response") \
FLT_OTEL_EVENT_DEF( HTTP_PROCESS_BE, RES, HRS_HDR, HRS_HDR, 1, "on-http-response") \
FLT_OTEL_EVENT_DEF( HTTP_HEADERS, RES, HRS_HDR, HRS_HDR, 1, "on-http-headers-response") \
FLT_OTEL_EVENT_DEF( HTTP_END, RES, , , 0, "on-http-end-response") \
FLT_OTEL_EVENT_DEF( HTTP_REPLY, RES, , , 0, "on-http-reply") \
/* FLT_OTEL_EVENT_DEF( HTTP_PROCESS_FE, RES, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( FLT_HTTP_HDRS, RES, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( HTTP_XFER_BODY, RES, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( WAIT_CLI, RES, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( FLT_XFER_DATA, RES, , , , "") */ \
/* FLT_OTEL_EVENT_DEF( FLT_END, RES, , , , "") */ \
FLT_OTEL_EVENT_DEF( SERVER_SESS_END, RES, , , 0, "on-server-session-end")
enum FLT_OTEL_EVENT_enum {
#define FLT_OTEL_EVENT_DEF(a,b,c,d,e,f) FLT_OTEL_EVENT_##b##_##a,
FLT_OTEL_EVENT_DEFINES
FLT_OTEL_EVENT_MAX
#undef FLT_OTEL_EVENT_DEF
};
/* Sample data types associated with a scope event. */
enum FLT_OTEL_EVENT_SAMPLE_enum {
FLT_OTEL_EVENT_SAMPLE_ATTRIBUTE = 0,
FLT_OTEL_EVENT_SAMPLE_EVENT,
FLT_OTEL_EVENT_SAMPLE_BAGGAGE,
FLT_OTEL_EVENT_SAMPLE_STATUS,
};
/* Per-event metadata mapping analyzer bits to filter event names. */
struct flt_otel_event_data {
uint an_bit; /* Used channel analyser. */
const char *an_name; /* Channel analyser name. */
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_otel_conf_scope;
/* Per-event metadata table indexed by FLT_OTEL_EVENT_* constants. */
extern const struct flt_otel_event_data flt_otel_event_data[FLT_OTEL_EVENT_MAX];
/* Execute a single scope: create spans, record instruments, evaluate samples. */
int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_otel_conf_scope *conf_scope, const struct timespec *ts_steady, const struct timespec *ts_system, uint dir, char **err);
/* Run all scopes matching a filter event on the given stream and channel. */
int flt_otel_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err);
#endif /* _OTEL_EVENT_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,55 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_FILTER_H_
#define _OTEL_FILTER_H_
#define FLT_OTEL_FMT_NAME "'" FLT_OTEL_OPT_NAME "' : "
#define FLT_OTEL_FMT_TYPE "'filter' : "
#define FLT_OTEL_VAR_UUID "sess", "otel", "uuid"
#define FLT_OTEL_ALERT(f, ...) ha_alert(FLT_OTEL_FMT_TYPE FLT_OTEL_FMT_NAME f "\n", ##__VA_ARGS__)
#define FLT_OTEL_CONDITION_IF "if"
#define FLT_OTEL_CONDITION_UNLESS "unless"
/* Return codes for OTel filter operations. */
enum FLT_OTEL_RET_enum {
FLT_OTEL_RET_ERROR = -1,
FLT_OTEL_RET_WAIT = 0,
FLT_OTEL_RET_IGNORE = 0,
FLT_OTEL_RET_OK = 1,
};
/* Dump or iterate a named configuration list for debugging. */
#define FLT_OTEL_DBG_LIST(d,m,p,t,v,f) \
do { \
if (LIST_ISEMPTY(&((d)->m##s))) { \
OTELC_DBG(DEBUG, p "- no " #m "s " t); \
} else { \
const struct flt_otel_conf_##m *v; \
\
OTELC_DBG(DEBUG, p "- " t " " #m "s: %s", \
flt_otel_list_dump(&((d)->m##s))); \
list_for_each_entry(v, &((d)->m##s), list) \
do { f; } while (0); \
} \
} while (0)
extern const char *otel_flt_id;
extern uint64_t flt_otel_drop_cnt;
extern struct flt_ops flt_otel_ops;
/* Check whether the OTel filter is disabled for a stream. */
bool flt_otel_is_disabled(const struct filter *f FLT_OTEL_DBG_ARGS(, int event));
#endif /* _OTEL_FILTER_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,46 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_GROUP_H_
#define _OTEL_GROUP_H_
#define FLT_OTEL_ACTION_GROUP "otel-group"
/* Argument indices for the otel-group action rule. */
enum FLT_OTEL_ARG_enum {
FLT_OTEL_ARG_FILTER_ID = 0,
FLT_OTEL_ARG_GROUP_ID,
FLT_OTEL_ARG_FLT_CONF = 0,
FLT_OTEL_ARG_CONF,
FLT_OTEL_ARG_GROUP,
};
/*
* A description of the macro arguments can be found in the structure
* flt_otel_group_data definition
*/
#define FLT_OTEL_GROUP_DEFINES \
FLT_OTEL_GROUP_DEF(ACT_F_TCP_REQ_CON, SMP_VAL_FE_CON_ACC, SMP_OPT_DIR_REQ) \
FLT_OTEL_GROUP_DEF(ACT_F_TCP_REQ_SES, SMP_VAL_FE_SES_ACC, SMP_OPT_DIR_REQ) \
FLT_OTEL_GROUP_DEF(ACT_F_TCP_REQ_CNT, SMP_VAL_FE_REQ_CNT, SMP_OPT_DIR_REQ) \
FLT_OTEL_GROUP_DEF(ACT_F_TCP_RES_CNT, SMP_VAL_BE_RES_CNT, SMP_OPT_DIR_RES) \
FLT_OTEL_GROUP_DEF(ACT_F_HTTP_REQ, SMP_VAL_FE_HRQ_HDR, SMP_OPT_DIR_REQ) \
FLT_OTEL_GROUP_DEF(ACT_F_HTTP_RES, SMP_VAL_BE_HRS_HDR, SMP_OPT_DIR_RES)
/* Per-action-from metadata mapping action types to fetch directions. */
struct flt_otel_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 /* _OTEL_GROUP_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,31 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_HTTP_H_
#define _OTEL_HTTP_H_
#ifndef DEBUG_OTEL
# define flt_otel_http_headers_dump(...) while (0)
#else
/* Dump all HTTP headers from a channel for debugging. */
void flt_otel_http_headers_dump(const struct channel *chn);
#endif
/* Extract HTTP headers matching a prefix into a text map. */
struct otelc_text_map *flt_otel_http_headers_get(struct channel *chn, const char *prefix, size_t len, char **err);
/* Set or replace an HTTP header in a channel. */
int flt_otel_http_header_set(struct channel *chn, const char *prefix, const char *name, const char *value, char **err);
/* Remove all HTTP headers matching a prefix from a channel. */
int flt_otel_http_headers_remove(struct channel *chn, const char *prefix, char **err);
#endif /* _OTEL_HTTP_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,54 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_INCLUDE_H_
#define _OTEL_INCLUDE_H_
#include <errno.h>
#include <stdbool.h>
#include <math.h>
#include <values.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 <opentelemetry-c-wrapper/include.h>
#include "config.h"
#include "debug.h"
#include "define.h"
#include "cli.h"
#include "event.h"
#include "conf.h"
#include "conf_funcs.h"
#include "filter.h"
#include "group.h"
#include "http.h"
#include "otelc.h"
#include "parser.h"
#include "pool.h"
#include "scope.h"
#include "util.h"
#include "vars.h"
#endif /* _OTEL_INCLUDE_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,27 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_OTELC_H_
#define _OTEL_OTELC_H_
/* Inject span context into a text map carrier. */
int flt_otel_inject_text_map(const struct otelc_span *span, struct otelc_text_map_writer *carrier);
/* Inject span context into an HTTP headers carrier. */
int flt_otel_inject_http_headers(const struct otelc_span *span, struct otelc_http_headers_writer *carrier);
/* Extract span context from a text map carrier. */
struct otelc_span_context *flt_otel_extract_text_map(struct otelc_tracer *tracer, struct otelc_text_map_reader *carrier, const struct otelc_text_map *text_map);
/* Extract span context from an HTTP headers carrier. */
struct otelc_span_context *flt_otel_extract_http_headers(struct otelc_tracer *tracer, struct otelc_http_headers_reader *carrier, const struct otelc_text_map *text_map);
#endif /* _OTEL_OTELC_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,221 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_PARSER_H_
#define _OTEL_PARSER_H_
#define FLT_OTEL_SCOPE "OTEL"
/*
* filter FLT_OTEL_OPT_NAME FLT_OTEL_OPT_FILTER_ID <FLT_OTEL_OPT_FILTER_ID_DEFAULT> FLT_OTEL_OPT_CONFIG <file>
*/
#define FLT_OTEL_OPT_NAME "opentelemetry"
#define FLT_OTEL_OPT_FILTER_ID "id"
#define FLT_OTEL_OPT_FILTER_ID_DEFAULT "otel-filter"
#define FLT_OTEL_OPT_CONFIG "config"
#define FLT_OTEL_PARSE_SECTION_INSTR_ID "otel-instrumentation"
#define FLT_OTEL_PARSE_SECTION_GROUP_ID "otel-group"
#define FLT_OTEL_PARSE_SECTION_SCOPE_ID "otel-scope"
#define FLT_OTEL_PARSE_SPAN_ROOT "root"
#define FLT_OTEL_PARSE_SPAN_PARENT "parent"
#define FLT_OTEL_PARSE_SPAN_LINK "link"
#define FLT_OTEL_PARSE_INSTRUMENT_DESC "desc"
#define FLT_OTEL_PARSE_INSTRUMENT_VALUE "value"
#define FLT_OTEL_PARSE_INSTRUMENT_ATTR "attr"
#define FLT_OTEL_PARSE_INSTRUMENT_UNIT "unit"
#define FLT_OTEL_PARSE_INSTRUMENT_BOUNDS "bounds"
#define FLT_OTEL_PARSE_INSTRUMENT_AGGR "aggr"
#define FLT_OTEL_PARSE_LOG_RECORD_ID "id"
#define FLT_OTEL_PARSE_LOG_RECORD_EVENT "event"
#define FLT_OTEL_PARSE_LOG_RECORD_SPAN "span"
#define FLT_OTEL_PARSE_LOG_RECORD_ATTR "attr"
#define FLT_OTEL_PARSE_CTX_AUTONAME "-"
#define FLT_OTEL_PARSE_CTX_IGNORE_NAME '-'
#define FLT_OTEL_PARSE_CTX_USE_HEADERS "use-headers"
#define FLT_OTEL_PARSE_CTX_USE_VARS "use-vars"
#define FLT_OTEL_PARSE_OPTION_HARDERR "hard-errors"
#define FLT_OTEL_PARSE_OPTION_DISABLED "disabled"
#define FLT_OTEL_PARSE_OPTION_NOLOGNORM "dontlog-normal"
/*
* A description of the macro arguments can be found in the structure
* flt_otel_parse_data definition
*/
#define FLT_OTEL_PARSE_INSTR_DEFINES \
FLT_OTEL_PARSE_INSTR_DEF( ID, 0, CHAR, 2, 2, "otel-instrumentation", " <name>") \
FLT_OTEL_PARSE_INSTR_DEF( ACL, 0, CHAR, 3, 0, "acl", " <name> <criterion> [flags] [operator] <value> ...") \
FLT_OTEL_PARSE_INSTR_DEF( LOG, 0, CHAR, 2, 0, "log", " { global | <addr> [len <len>] [format <fmt>] <facility> [<level> [<minlevel>]] }") \
FLT_OTEL_PARSE_INSTR_DEF( CONFIG, 0, NONE, 2, 2, "config", " <file>") \
FLT_OTEL_PARSE_INSTR_DEF( GROUPS, 0, NONE, 2, 0, "groups", " <name> ...") \
FLT_OTEL_PARSE_INSTR_DEF( SCOPES, 0, NONE, 2, 0, "scopes", " <name> ...") \
FLT_OTEL_PARSE_INSTR_DEF( RATE_LIMIT, 0, NONE, 2, 2, "rate-limit", " <value>") \
FLT_OTEL_PARSE_INSTR_DEF( OPTION, 0, NONE, 2, 2, "option", " { disabled | dontlog-normal | hard-errors }") \
FLT_OTEL_PARSE_INSTR_DEF(DEBUG_LEVEL, 0, NONE, 2, 2, "debug-level", " <value>")
#define FLT_OTEL_PARSE_GROUP_DEFINES \
FLT_OTEL_PARSE_GROUP_DEF( ID, 0, CHAR, 2, 2, "otel-group", " <name>") \
FLT_OTEL_PARSE_GROUP_DEF(SCOPES, 0, NONE, 2, 0, "scopes", " <name> ...")
#ifdef USE_OTEL_VARS
# define FLT_OTEL_PARSE_SCOPE_INJECT_HELP " <name-prefix> [use-vars] [use-headers]"
# define FLT_OTEL_PARSE_SCOPE_EXTRACT_HELP " <name-prefix> [use-vars | use-headers]"
#else
# define FLT_OTEL_PARSE_SCOPE_INJECT_HELP " <name-prefix> [use-headers]"
# define FLT_OTEL_PARSE_SCOPE_EXTRACT_HELP " <name-prefix> [use-headers]"
#endif
/*
* The first argument of the FLT_OTEL_PARSE_SCOPE_STATUS_DEF() macro is defined
* as otelc_span_status_t in <opentelemetry-c-wrapper/span.h> .
*/
#define FLT_OTEL_PARSE_SCOPE_STATUS_DEFINES \
FLT_OTEL_PARSE_SCOPE_STATUS_DEF(IGNORE, "ignore") \
FLT_OTEL_PARSE_SCOPE_STATUS_DEF( UNSET, "unset" ) \
FLT_OTEL_PARSE_SCOPE_STATUS_DEF( OK, "ok" ) \
FLT_OTEL_PARSE_SCOPE_STATUS_DEF( ERROR, "error" )
/* Sentinel: instrument has not been created yet. */
#define OTELC_METRIC_INSTRUMENT_UNSET -1
/* Sentinel: instrument creation is in progress by another thread. */
#define OTELC_METRIC_INSTRUMENT_PENDING -2
/* Sentinel: update-form instrument (re-evaluates an existing one). */
#define OTELC_METRIC_INSTRUMENT_UPDATE 0xff
#define OTELC_METRIC_AGGREGATION_UNSET -1
/*
* Observable (asynchronous) instruments are not supported. The OTel SDK
* invokes their callbacks from an external background thread that is not a
* HAProxy thread. HAProxy sample fetches rely on internal per-thread-group
* state and return incorrect results when called from a non-HAProxy thread.
*
* Double-precision instruments are not supported because HAProxy sample fetches
* do not return double values.
*/
#define FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEFINES \
FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(UPDATE, "update" ) \
FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(COUNTER_UINT64, "cnt_int" ) \
FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(HISTOGRAM_UINT64, "hist_int" ) \
FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(UDCOUNTER_INT64, "udcnt_int") \
FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(GAUGE_INT64, "gauge_int")
/*
* In case the possibility of working with OpenTelemetry context via HAProxy
* variables is not used, args_max member of the structure flt_otel_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_OTEL_PARSE_SCOPE_DEFINES \
FLT_OTEL_PARSE_SCOPE_DEF( ID, 0, CHAR, 2, 2, "otel-scope", " <name>") \
FLT_OTEL_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 7, "span", " <name> [<reference>] [<link>] [root]") \
FLT_OTEL_PARSE_SCOPE_DEF( LINK, 1, NONE, 2, 0, "link", " <span> ...") \
FLT_OTEL_PARSE_SCOPE_DEF( ATTRIBUTE, 1, NONE, 3, 0, "attribute", " <key> <sample> ...") \
FLT_OTEL_PARSE_SCOPE_DEF( EVENT, 1, NONE, 4, 0, "event", " <name> <key> <sample> ...") \
FLT_OTEL_PARSE_SCOPE_DEF( BAGGAGE, 1, VAR, 3, 0, "baggage", " <key> <sample> ...") \
FLT_OTEL_PARSE_SCOPE_DEF( INJECT, 1, CTX, 2, 4, "inject", FLT_OTEL_PARSE_SCOPE_INJECT_HELP) \
FLT_OTEL_PARSE_SCOPE_DEF( EXTRACT, 0, CTX, 2, 3, "extract", FLT_OTEL_PARSE_SCOPE_EXTRACT_HELP) \
FLT_OTEL_PARSE_SCOPE_DEF( STATUS, 1, NONE, 2, 0, "status", " <code> [<sample> ...]") \
FLT_OTEL_PARSE_SCOPE_DEF( FINISH, 0, NONE, 2, 0, "finish", " <name> ...") \
FLT_OTEL_PARSE_SCOPE_DEF( INSTRUMENT, 0, NONE, 3, 0, "instrument", " { update <name> [<attr> ...] | <type> <name> [<aggr>] [<desc>] [<unit>] <value> [<bounds>] }") \
FLT_OTEL_PARSE_SCOPE_DEF( LOG_RECORD, 0, NONE, 3, 0, "log-record", " <severity> [<id>] [<event>] [<span>] [<attr>] <sample> ...") \
FLT_OTEL_PARSE_SCOPE_DEF(IDLE_TIMEOUT, 0, NONE, 2, 2, "idle-timeout", " <time>") \
FLT_OTEL_PARSE_SCOPE_DEF( ACL, 0, CHAR, 3, 0, "acl", " <name> <criterion> [flags] [operator] <value> ...") \
FLT_OTEL_PARSE_SCOPE_DEF( ON_EVENT, 0, NONE, 2, 0, "otel-event", " <name> [{ if | unless } <condition>]")
/* Invalid character check modes for identifier validation. */
enum FLT_OTEL_PARSE_INVCHAR_enum {
FLT_OTEL_PARSE_INVALID_NONE,
FLT_OTEL_PARSE_INVALID_CHAR,
FLT_OTEL_PARSE_INVALID_DOM,
FLT_OTEL_PARSE_INVALID_CTX,
FLT_OTEL_PARSE_INVALID_VAR,
};
enum FLT_OTEL_PARSE_INSTR_enum {
#define FLT_OTEL_PARSE_INSTR_DEF(a,b,c,d,e,f,g) FLT_OTEL_PARSE_INSTR_##a,
FLT_OTEL_PARSE_INSTR_DEFINES
#undef FLT_OTEL_PARSE_INSTR_DEF
};
enum FLT_OTEL_PARSE_GROUP_enum {
#define FLT_OTEL_PARSE_GROUP_DEF(a,b,c,d,e,f,g) FLT_OTEL_PARSE_GROUP_##a,
FLT_OTEL_PARSE_GROUP_DEFINES
#undef FLT_OTEL_PARSE_GROUP_DEF
};
enum FLT_OTEL_PARSE_SCOPE_enum {
#define FLT_OTEL_PARSE_SCOPE_DEF(a,b,c,d,e,f,g) FLT_OTEL_PARSE_SCOPE_##a,
FLT_OTEL_PARSE_SCOPE_DEFINES
#undef FLT_OTEL_PARSE_SCOPE_DEF
};
/* Context storage type flags for inject/extract operations. */
enum FLT_OTEL_CTX_USE_enum {
FLT_OTEL_CTX_USE_VARS = 1 << 0,
FLT_OTEL_CTX_USE_HEADERS = 1 << 1,
};
/* Logging state flags for the OTel filter. */
enum FLT_OTEL_LOGGING_enum {
FLT_OTEL_LOGGING_OFF = 0,
FLT_OTEL_LOGGING_ON = 1 << 0,
FLT_OTEL_LOGGING_NOLOGNORM = 1 << 1,
};
/* Keyword metadata used by the configuration section parsers. */
struct flt_otel_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_OTEL_PARSE_KEYWORD(n,s) (strcmp(args[n], (s)) == 0)
#define FLT_OTEL_PARSE_WARNING(f, ...) \
ha_warning("parsing [%s:%d] : " FLT_OTEL_FMT_TYPE FLT_OTEL_FMT_NAME "'" f "'\n", ##__VA_ARGS__);
#define FLT_OTEL_PARSE_ALERT(f, ...) \
do { \
ha_alert("parsing [%s:%d] : " FLT_OTEL_FMT_TYPE FLT_OTEL_FMT_NAME "'" f "'\n", ##__VA_ARGS__); \
\
retval |= ERR_ABORT | ERR_ALERT; \
} while (0)
#define FLT_OTEL_POST_PARSE_ALERT(f, ...) \
FLT_OTEL_PARSE_ALERT(f, flt_otel_current_config->cfg_file, ##__VA_ARGS__)
#define FLT_OTEL_PARSE_ERR(e,f, ...) \
do { \
if (*(e) == NULL) \
(void)memprintf((e), f, ##__VA_ARGS__); \
\
retval |= ERR_ABORT | ERR_ALERT; \
} while (0)
#define FLT_OTEL_PARSE_IFERR_ALERT() \
do { \
if (err == NULL) \
break; \
\
FLT_OTEL_PARSE_ALERT("%s", file, line, err); \
FLT_OTEL_ERR_FREE(err); \
} while (0)
#endif /* _OTEL_PARSER_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,71 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_POOL_H_
#define _OTEL_POOL_H_
#define FLT_OTEL_POOL_INIT(p,n,s,r) \
do { \
if (((r) == FLT_OTEL_RET_OK) && ((p) == NULL)) { \
(p) = create_pool(n, (s), MEM_F_SHARED); \
if ((p) == NULL) \
(r) = FLT_OTEL_RET_ERROR; \
\
OTELC_DBG(DEBUG, #p " %p %u", (p), FLT_OTEL_DEREF((p), size, 0)); \
} \
} while (0)
#define FLT_OTEL_POOL_DESTROY(p) \
do { \
if ((p) != NULL) { \
OTELC_DBG(DEBUG, #p " %p %u", (p), (p)->size); \
\
pool_destroy(p); \
(p) = NULL; \
} \
} while (0)
extern struct pool_head *pool_head_otel_scope_span __read_mostly;
extern struct pool_head *pool_head_otel_scope_context __read_mostly;
extern struct pool_head *pool_head_otel_runtime_context __read_mostly;
extern struct pool_head *pool_head_otel_span_context __read_mostly;
/* Allocate memory from a pool with optional zeroing. */
void *flt_otel_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err);
/* Duplicate a string into pool-allocated memory. */
void *flt_otel_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err);
/* Release pool-allocated memory and clear the pointer. */
void flt_otel_pool_free(struct pool_head *pool, void **ptr);
/* Initialize OTel filter memory pools. */
int flt_otel_pool_init(void);
/* Destroy OTel filter memory pools. */
void flt_otel_pool_destroy(void);
/* Log debug information about OTel filter memory pools. */
#ifndef DEBUG_OTEL
# define flt_otel_pool_info() while (0)
#else
void flt_otel_pool_info(void);
#endif
/* Allocate a trash buffer with optional zeroing. */
struct buffer *flt_otel_trash_alloc(bool flag_clear, char **err);
/* Release a trash buffer and clear the pointer. */
void flt_otel_trash_free(struct buffer **ptr);
#endif /* _OTEL_POOL_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,174 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_SCOPE_H_
#define _OTEL_SCOPE_H_
#define FLT_OTEL_SCOPE_SPAN_FINISH_REQ "*req*"
#define FLT_OTEL_SCOPE_SPAN_FINISH_RES "*res*"
#define FLT_OTEL_SCOPE_SPAN_FINISH_ALL "*"
#define FLT_OTEL_RT_CTX(p) ((struct flt_otel_runtime_context *)(p))
#define FLT_OTEL_DBG_SCOPE_SPAN(h,p) \
OTELC_DBG(DEBUG, h "%p:{ '%s' %zu %u %hhu %p %p %p }", (p), \
FLT_OTEL_STR_HDR_ARGS(p, id), (p)->smp_opt_dir, \
(p)->flag_finish, (p)->span, (p)->ref_span, (p)->ref_ctx)
#define FLT_OTEL_DBG_SCOPE_CONTEXT(h,p) \
OTELC_DBG(DEBUG, h "%p:{ '%s' %zu %u %hhu %p }", (p), \
FLT_OTEL_STR_HDR_ARGS(p, id), (p)->smp_opt_dir, \
(p)->flag_finish, (p)->context)
#define FLT_OTEL_DBG_SCOPE_DATA_EVENT(h,p) \
OTELC_DBG(DEBUG, h "%p:{ '%s' %p %zu %zu %s }", &(p), \
(p).name, (p).attr, (p).cnt, (p).size, \
flt_otel_list_dump(&((p).list)))
#define FLT_OTEL_DBG_SCOPE_DATA_STATUS(h,p) \
OTELC_DBG(DEBUG, h "%p:{ %d '%s' }", (p), (p)->code, OTELC_STR_ARG((p)->description))
#define FLT_OTEL_DBG_SCOPE_DATA_KV_FMT "%p:{ %p %zu %zu }"
#define FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS(p) &(p), (p).attr, (p).cnt, (p).size
#define FLT_OTEL_DBG_SCOPE_DATA(h,p) \
OTELC_DBG(DEBUG, h "%p:{ " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " %s %s }", (p), \
FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->baggage), FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->attributes), \
flt_otel_list_dump(&((p)->events)), flt_otel_list_dump(&((p)->links)))
#define FLT_OTEL_DBG_RUNTIME_CONTEXT(h,p) \
OTELC_DBG(DEBUG, h "%p:{ %p %p '%s' %hhu %hhu 0x%02hhx 0x%08x %u %d %s %s }", (p), \
(p)->stream, (p)->filter, (p)->uuid, (p)->flag_harderr, (p)->flag_disabled, \
(p)->logging, (p)->analyzers, (p)->idle_timeout, (p)->idle_exp, \
flt_otel_list_dump(&((p)->spans)), flt_otel_list_dump(&((p)->contexts)))
/* Anonymous struct containing a const string pointer and its length. */
#define FLT_OTEL_CONST_STR_HDR(p) \
struct { \
const char *p; \
size_t p##_len; \
}
/* Growable key-value array for span attributes or baggage. */
struct flt_otel_scope_data_kv {
struct otelc_kv *attr; /* Key-value array for storing attributes. */
size_t cnt; /* Number of currently used array elements. */
size_t size; /* Total number of array elements. */
};
/* Named event with its own key-value attribute array. */
struct flt_otel_scope_data_event {
char *name; /* Event name, not used for other data types. */
struct otelc_kv *attr; /* Key-value array for storing attributes. */
size_t cnt; /* Number of currently used array elements. */
size_t size; /* Total number of array elements. */
struct list list; /* Used to chain this structure. */
};
/* Span link referencing another span or span context. */
struct flt_otel_scope_data_link {
struct otelc_span *span; /* Linked span, or NULL. */
struct otelc_span_context *context; /* Linked span context, or NULL. */
struct list list; /* Used to chain this structure. */
};
/* Span status code and description. */
struct flt_otel_scope_data_status {
int code; /* OTELC_SPAN_STATUS_* value. */
char *description; /* Span status description string. */
};
/* Aggregated runtime data collected during scope execution. */
struct flt_otel_scope_data {
struct flt_otel_scope_data_kv baggage; /* Defined scope baggage. */
struct flt_otel_scope_data_kv attributes; /* Defined scope attributes. */
struct list events; /* Defined scope events. */
struct list links; /* Defined scope links. */
struct flt_otel_scope_data_status status; /* Defined scope status. */
};
/* flt_otel_runtime_context->spans */
struct flt_otel_scope_span {
FLT_OTEL_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 otelc_span *span; /* The current span. */
struct otelc_span *ref_span; /* Span to which the current span refers. */
struct otelc_span_context *ref_ctx; /* Span context to which the current span refers. */
struct list list; /* Used to chain this structure. */
};
/* flt_otel_runtime_context->contexts */
struct flt_otel_scope_context {
FLT_OTEL_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 otelc_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_otel_runtime_context {
struct stream *stream; /* The stream to which the filter is attached. */
struct filter *filter; /* The OpenTelemetry 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. */
uint idle_timeout; /* Idle timeout interval in milliseconds (0 = off). */
int idle_exp; /* Tick at which the next idle timeout fires. */
struct list spans; /* The scope spans. */
struct list contexts; /* The scope contexts. */
};
#ifndef DEBUG_OTEL
# define flt_otel_scope_data_dump(...) while (0)
#else
/* Dump scope data contents for debugging. */
void flt_otel_scope_data_dump(const struct flt_otel_scope_data *data);
#endif
/* Allocate and initialize a runtime context for a stream. */
struct flt_otel_runtime_context *flt_otel_runtime_context_init(struct stream *s, struct filter *f, char **err);
/* Free the runtime context attached to a filter. */
void flt_otel_runtime_context_free(struct filter *f);
/* Allocate and initialize a scope span in the runtime context. */
struct flt_otel_scope_span *flt_otel_scope_span_init(struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len, const char *ref_id, size_t ref_id_len, uint dir, char **err);
/* Free a scope span and remove it from the runtime context. */
void flt_otel_scope_span_free(struct flt_otel_scope_span **ptr);
/* Allocate and initialize a scope context in the runtime context. */
struct flt_otel_scope_context *flt_otel_scope_context_init(struct flt_otel_runtime_context *rt_ctx, struct otelc_tracer *tracer, const char *id, size_t id_len, const struct otelc_text_map *text_map, uint dir, char **err);
/* Free a scope context and remove it from the runtime context. */
void flt_otel_scope_context_free(struct flt_otel_scope_context **ptr);
/* Initialize scope data arrays and lists. */
void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr);
/* Free all scope data contents. */
void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr);
/* Mark a span for finishing by name in the runtime context. */
int flt_otel_scope_finish_mark(const struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len);
/* End all spans that have been marked for finishing. */
void flt_otel_scope_finish_marked(const struct flt_otel_runtime_context *rt_ctx, const struct timespec *ts_finish);
/* Free scope spans and contexts no longer needed by a channel. */
void flt_otel_scope_free_unused(struct flt_otel_runtime_context *rt_ctx, struct channel *chn);
#endif /* _OTEL_SCOPE_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,104 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_UTIL_H_
#define _OTEL_UTIL_H_
#define FLT_OTEL_HTTP_METH_DEFINES \
FLT_OTEL_HTTP_METH_DEF(OPTIONS) \
FLT_OTEL_HTTP_METH_DEF(GET) \
FLT_OTEL_HTTP_METH_DEF(HEAD) \
FLT_OTEL_HTTP_METH_DEF(POST) \
FLT_OTEL_HTTP_METH_DEF(PUT) \
FLT_OTEL_HTTP_METH_DEF(DELETE) \
FLT_OTEL_HTTP_METH_DEF(TRACE) \
FLT_OTEL_HTTP_METH_DEF(CONNECT)
/* Iterate over all OTel filter configurations across all proxies. */
#define FLT_OTEL_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 == otel_flt_id) { \
struct flt_otel_conf *conf = fconf->conf;
#define FLT_OTEL_PROXIES_LIST_END() \
} \
} while (0)
#ifdef DEBUG_OTEL
# define FLT_OTEL_ARGS_DUMP() do { if (otelc_dbg_level & (1 << OTELC_DBG_LEVEL_LOG)) flt_otel_args_dump((const char **)args); } while (0)
#else
# define FLT_OTEL_ARGS_DUMP() while (0)
#endif
#ifndef DEBUG_OTEL
# define flt_otel_filters_dump() while (0)
#else
/* Dump configuration arguments for debugging. */
void flt_otel_args_dump(const char **args);
/* Dump all OTel filter configurations across all proxies. */
void flt_otel_filters_dump(void);
/* Return a label string identifying a channel direction. */
const char *flt_otel_chn_label(const struct channel *chn);
/* Return the proxy mode string for a stream. */
const char *flt_otel_pr_mode(const struct stream *s);
/* Return the stream processing position as a string. */
const char *flt_otel_stream_pos(const struct stream *s);
/* Return the filter type string for a filter instance. */
const char *flt_otel_type(const struct filter *f);
/* Return the analyzer name string for an analyzer bit. */
const char *flt_otel_analyzer(uint an_bit);
/* Dump a linked list of configuration items as a string. */
const char *flt_otel_list_dump(const struct list *head);
#endif
/* Count the number of non-NULL arguments in an argument array. */
int flt_otel_args_count(const char **args);
/* Concatenate argument array elements into a single string. */
int flt_otel_args_concat(const char **args, int idx, int n, char **str);
/* Comparator for qsort: ascending order of doubles with epsilon tolerance. */
int flt_otel_qsort_compar_double(const void *a, const void *b);
/* Parse a string to double with range validation. */
bool flt_otel_strtod(const char *nptr, double *value, double limit_min, double limit_max, char **err);
/* Parse a string to int64_t with range validation. */
bool flt_otel_strtoll(const char *nptr, int64_t *value, int64_t limit_min, int64_t limit_max, char **err);
/* Convert sample data to a string representation. */
int flt_otel_sample_to_str(const struct sample_data *data, char *value, size_t size, char **err);
/* Convert sample data to an OTel value. */
int flt_otel_sample_to_value(const char *key, const struct sample_data *data, struct otelc_value *value, char **err);
/* Add a key-value pair to a growable key-value array. */
int flt_otel_sample_add_kv(struct flt_otel_scope_data_kv *kv, const char *key, const struct otelc_value *value);
/* Evaluate a sample definition into an OTel value. */
int flt_otel_sample_eval(struct stream *s, uint dir, struct flt_otel_conf_sample *sample, bool flag_native, struct otelc_value *value, char **err);
/* Evaluate a sample expression and add the result to scope data. */
int flt_otel_sample_add(struct stream *s, uint dir, struct flt_otel_conf_sample *sample, struct flt_otel_scope_data *data, int type, char **err);
#endif /* _OTEL_UTIL_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,52 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_VARS_H_
#define _OTEL_VARS_H_
#define FLT_OTEL_VARS_SCOPE "txn"
#define FLT_OTEL_VAR_CHAR_DASH 'D'
#define FLT_OTEL_VAR_CHAR_SPACE 'S'
#ifndef USE_OTEL_VARS_NAME
# define FLT_OTEL_VAR_CTX_SIZE int8_t
/* Context buffer for storing a single variable value during iteration. */
struct flt_otel_ctx {
char value[BUFSIZ]; /* Variable value string. */
int value_len; /* Length of the value string. */
};
/* Callback type invoked for each context variable during iteration. */
typedef int (*flt_otel_ctx_loop_cb)(struct sample *, size_t, const char *, const char *, const char *, FLT_OTEL_VAR_CTX_SIZE, char **, void *);
#endif /* !USE_OTEL_VARS_NAME */
#ifndef DEBUG_OTEL
# define flt_otel_vars_dump(...) while (0)
#else
/* Dump all OTel-related variables for a stream. */
void flt_otel_vars_dump(struct stream *s);
#endif
/* Register a HAProxy variable for OTel context storage. */
int flt_otel_var_register(const char *scope, const char *prefix, const char *name, char **err);
/* Set an OTel context variable on a stream. */
int flt_otel_var_set(struct stream *s, const char *scope, const char *prefix, const char *name, const char *value, uint opt, char **err);
/* Unset all OTel context variables matching a prefix on a stream. */
int flt_otel_vars_unset(struct stream *s, const char *scope, const char *prefix, uint opt, char **err);
/* Retrieve all OTel context variables matching a prefix into a text map. */
struct otelc_text_map *flt_otel_vars_get(struct stream *s, const char *scope, const char *prefix, uint opt, char **err);
#endif /* _OTEL_VARS_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,457 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
/***
* NAME
* flt_otel_cli_set_msg - CLI response message setter
*
* SYNOPSIS
* static int flt_otel_cli_set_msg(struct appctx *appctx, char *err, char *msg)
*
* ARGUMENTS
* appctx - CLI application context
* err - error message string (or NULL)
* msg - informational message string (or NULL)
*
* DESCRIPTION
* Sets the CLI response message and state for the given <appctx>. If <err>
* is non-NULL, it is passed to cli_dynerr() and <msg> is freed; otherwise
* <msg> is passed to cli_dynmsg() at LOG_INFO severity. When neither message
* is available, the function returns 0 without changing state.
*
* RETURN VALUE
* Returns 1 when a message was set, or 0 when both pointers were NULL.
*/
static int flt_otel_cli_set_msg(struct appctx *appctx, char *err, char *msg)
{
OTELC_FUNC("%p, %p, %p", appctx, err, msg);
if ((appctx == NULL) || ((err == NULL) && (msg == NULL)))
OTELC_RETURN_INT(0);
if (err != NULL) {
OTELC_DBG(INFO, "err(%d): \"%s\"", appctx->st0, err);
OTELC_SFREE(msg);
OTELC_RETURN_INT(cli_dynerr(appctx, err));
}
OTELC_DBG(INFO, "msg(%d): \"%s\"", appctx->st0, msg);
OTELC_RETURN_INT(cli_dynmsg(appctx, LOG_INFO, msg));
}
#ifdef DEBUG_OTEL
/***
* NAME
* flt_otel_cli_parse_debug - CLI debug level handler
*
* SYNOPSIS
* static int flt_otel_cli_parse_debug(char **args, char *payload, struct appctx *appctx, void *private)
*
* ARGUMENTS
* args - CLI command arguments array
* payload - CLI command payload string
* appctx - CLI application context
* private - unused private data pointer
*
* DESCRIPTION
* Handles the "otel debug [level]" CLI command. When a level argument is
* provided in <args[2]>, parses it as an integer in the range
* [0, OTELC_DBG_LEVEL_MASK] and atomically stores it as the global debug
* level. Setting a level requires admin access level. When no argument is
* given, reports the current debug level. The response message includes the
* debug level in both decimal and hexadecimal format.
*
* RETURN VALUE
* Returns 1, or 0 on memory allocation failure.
*/
static int flt_otel_cli_parse_debug(char **args, char *payload, struct appctx *appctx, void *private)
{
char *err = NULL, *msg = NULL;
OTELC_FUNC("%p, \"%s\", %p, %p", args, OTELC_STR_ARG(payload), appctx, private);
FLT_OTEL_ARGS_DUMP();
if (FLT_OTEL_ARG_ISVALID(2)) {
int64_t value;
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
OTELC_RETURN_INT(1);
if (flt_otel_strtoll(args[2], &value, 0, OTELC_DBG_LEVEL_MASK, &err)) {
_HA_ATOMIC_STORE(&otelc_dbg_level, (int)value);
(void)memprintf(&msg, FLT_OTEL_CLI_CMD " : debug level set to %d (0x%04x)", (int)value, (int)value);
}
} else {
int value = _HA_ATOMIC_LOAD(&otelc_dbg_level);
(void)memprintf(&msg, FLT_OTEL_CLI_CMD " : current debug level is %d (0x%04x)", value, value);
}
OTELC_RETURN_INT(flt_otel_cli_set_msg(appctx, err, msg));
}
#endif /* DEBUG_OTEL */
/***
* NAME
* flt_otel_cli_parse_disabled - CLI enable/disable handler
*
* SYNOPSIS
* static int flt_otel_cli_parse_disabled(char **args, char *payload, struct appctx *appctx, void *private)
*
* ARGUMENTS
* args - CLI command arguments array
* payload - CLI command payload string
* appctx - CLI application context
* private - boolean flag cast to pointer (1 = disable, 0 = enable)
*
* DESCRIPTION
* Handles the "otel enable" and "otel disable" CLI commands. The <private>
* parameter determines the action: a value of 1 disables the filter, 0
* enables it. Requires admin access level. The flag_disabled field is
* atomically updated for all OTel filter instances across all proxies.
*
* RETURN VALUE
* Returns 1, or 0 if no OTel filter instances are configured or on memory
* allocation failure.
*/
static int flt_otel_cli_parse_disabled(char **args, char *payload, struct appctx *appctx, void *private)
{
char *msg = NULL;
bool value = (uintptr_t)private;
OTELC_FUNC("%p, \"%s\", %p, %p", args, OTELC_STR_ARG(payload), appctx, private);
FLT_OTEL_ARGS_DUMP();
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
OTELC_RETURN_INT(1);
FLT_OTEL_PROXIES_LIST_START() {
_HA_ATOMIC_STORE(&(conf->instr->flag_disabled), value);
(void)memprintf(&msg, "%s%s" FLT_OTEL_CLI_CMD " : filter %sabled", FLT_OTEL_CLI_MSG_CAT(msg), value ? "dis" : "en");
} FLT_OTEL_PROXIES_LIST_END();
OTELC_RETURN_INT(flt_otel_cli_set_msg(appctx, NULL, msg));
}
/***
* NAME
* flt_otel_cli_parse_option - CLI error mode handler
*
* SYNOPSIS
* static int flt_otel_cli_parse_option(char **args, char *payload, struct appctx *appctx, void *private)
*
* ARGUMENTS
* args - CLI command arguments array
* payload - CLI command payload string
* appctx - CLI application context
* private - boolean flag cast to pointer (1 = hard-errors, 0 = soft-errors)
*
* DESCRIPTION
* Handles the "otel hard-errors" and "otel soft-errors" CLI commands. The
* <private> parameter determines the error mode: a value of 1 enables
* hard-error mode (filter failure aborts the stream), 0 enables soft-error
* mode (failures are silently ignored). Requires admin access level. The
* flag_harderr field is atomically updated for all OTel filter instances
* across all proxies.
*
* RETURN VALUE
* Returns 1, or 0 if no OTel filter instances are configured or on memory
* allocation failure.
*/
static int flt_otel_cli_parse_option(char **args, char *payload, struct appctx *appctx, void *private)
{
char *msg = NULL;
bool value = (uintptr_t)private;
OTELC_FUNC("%p, \"%s\", %p, %p", args, OTELC_STR_ARG(payload), appctx, private);
FLT_OTEL_ARGS_DUMP();
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
OTELC_RETURN_INT(1);
FLT_OTEL_PROXIES_LIST_START() {
_HA_ATOMIC_STORE(&(conf->instr->flag_harderr), value);
(void)memprintf(&msg, "%s%s" FLT_OTEL_CLI_CMD " : filter set %s-errors", FLT_OTEL_CLI_MSG_CAT(msg), value ? "hard" : "soft");
} FLT_OTEL_PROXIES_LIST_END();
OTELC_RETURN_INT(flt_otel_cli_set_msg(appctx, NULL, msg));
}
/***
* NAME
* flt_otel_cli_parse_logging - CLI logging state handler
*
* SYNOPSIS
* static int flt_otel_cli_parse_logging(char **args, char *payload, struct appctx *appctx, void *private)
*
* ARGUMENTS
* args - CLI command arguments array
* payload - CLI command payload string
* appctx - CLI application context
* private - unused private data pointer
*
* DESCRIPTION
* Handles the "otel logging [state]" CLI command. When a state argument is
* provided in <args[2]>, it is matched against "off", "on", or "nolognorm"
* and the logging field is atomically updated for all OTel filter instances.
* Setting a value requires admin access level. When no argument is given,
* reports the current logging state for all instances. Invalid values
* produce an error with the accepted options listed.
*
* RETURN VALUE
* Returns 1, or 0 if no OTel filter instances are configured (and no error
* occurred) or on memory allocation failure.
*/
static int flt_otel_cli_parse_logging(char **args, char *payload, struct appctx *appctx, void *private)
{
char *err = NULL, *msg = NULL;
bool flag_set = false;
uint8_t value;
OTELC_FUNC("%p, \"%s\", %p, %p", args, OTELC_STR_ARG(payload), appctx, private);
FLT_OTEL_ARGS_DUMP();
if (FLT_OTEL_ARG_ISVALID(2)) {
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
OTELC_RETURN_INT(1);
if (strcasecmp(args[2], FLT_OTEL_CLI_LOGGING_OFF) == 0) {
flag_set = true;
value = FLT_OTEL_LOGGING_OFF;
}
else if (strcasecmp(args[2], FLT_OTEL_CLI_LOGGING_ON) == 0) {
flag_set = true;
value = FLT_OTEL_LOGGING_ON;
}
else if (strcasecmp(args[2], FLT_OTEL_CLI_LOGGING_NOLOGNORM) == 0) {
flag_set = true;
value = FLT_OTEL_LOGGING_ON | FLT_OTEL_LOGGING_NOLOGNORM;
}
else {
(void)memprintf(&err, "'%s' : invalid value, use <" FLT_OTEL_CLI_LOGGING_OFF " | " FLT_OTEL_CLI_LOGGING_ON " | " FLT_OTEL_CLI_LOGGING_NOLOGNORM ">", args[2]);
}
if (flag_set) {
FLT_OTEL_PROXIES_LIST_START() {
_HA_ATOMIC_STORE(&(conf->instr->logging), value);
(void)memprintf(&msg, "%s%s" FLT_OTEL_CLI_CMD " : logging is %s", FLT_OTEL_CLI_MSG_CAT(msg), FLT_OTEL_CLI_LOGGING_STATE(value));
} FLT_OTEL_PROXIES_LIST_END();
}
} else {
FLT_OTEL_PROXIES_LIST_START() {
value = _HA_ATOMIC_LOAD(&(conf->instr->logging));
(void)memprintf(&msg, "%s%s" FLT_OTEL_CLI_CMD " : logging is currently %s", FLT_OTEL_CLI_MSG_CAT(msg), FLT_OTEL_CLI_LOGGING_STATE(value));
} FLT_OTEL_PROXIES_LIST_END();
}
OTELC_RETURN_INT(flt_otel_cli_set_msg(appctx, err, msg));
}
/***
* NAME
* flt_otel_cli_parse_rate - CLI rate limit handler
*
* SYNOPSIS
* static int flt_otel_cli_parse_rate(char **args, char *payload, struct appctx *appctx, void *private)
*
* ARGUMENTS
* args - CLI command arguments array
* payload - CLI command payload string
* appctx - CLI application context
* private - unused private data pointer
*
* DESCRIPTION
* Handles the "otel rate [value]" CLI command. When a value argument is
* provided in <args[2]>, it is parsed as a floating-point number in the
* range [0.0, 100.0], converted to a fixed-point uint32_t representation,
* and atomically stored as the rate limit for all OTel filter instances.
* Setting a value requires admin access level. When no argument is given,
* reports the current rate limit percentage for all instances.
*
* RETURN VALUE
* Returns 1, or 0 if no OTel filter instances are configured (and no error
* occurred) or on memory allocation failure.
*/
static int flt_otel_cli_parse_rate(char **args, char *payload, struct appctx *appctx, void *private)
{
char *err = NULL, *msg = NULL;
OTELC_FUNC("%p, \"%s\", %p, %p", args, OTELC_STR_ARG(payload), appctx, private);
FLT_OTEL_ARGS_DUMP();
if (FLT_OTEL_ARG_ISVALID(2)) {
double value;
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
OTELC_RETURN_INT(1);
if (flt_otel_strtod(args[2], &value, 0.0, 100.0, &err)) {
FLT_OTEL_PROXIES_LIST_START() {
_HA_ATOMIC_STORE(&(conf->instr->rate_limit), FLT_OTEL_FLOAT_U32(value));
(void)memprintf(&msg, "%s%s" FLT_OTEL_CLI_CMD " : rate limit set to %.2f", FLT_OTEL_CLI_MSG_CAT(msg), value);
} FLT_OTEL_PROXIES_LIST_END();
}
} else {
FLT_OTEL_PROXIES_LIST_START() {
uint32_t value = _HA_ATOMIC_LOAD(&(conf->instr->rate_limit));
(void)memprintf(&msg, "%s%s" FLT_OTEL_CLI_CMD " : current rate limit is %.2f", FLT_OTEL_CLI_MSG_CAT(msg), FLT_OTEL_U32_FLOAT(value));
} FLT_OTEL_PROXIES_LIST_END();
}
OTELC_RETURN_INT(flt_otel_cli_set_msg(appctx, err, msg));
}
/***
* NAME
* flt_otel_cli_parse_status - CLI status display handler
*
* SYNOPSIS
* static int flt_otel_cli_parse_status(char **args, char *payload, struct appctx *appctx, void *private)
*
* ARGUMENTS
* args - CLI command arguments array
* payload - CLI command payload string
* appctx - CLI application context
* private - unused private data pointer
*
* DESCRIPTION
* Handles the "otel status" CLI command. Builds a formatted status report
* for all OTel filter instances across all proxies. The report includes
* the library version, proxy name, configuration file path, group and scope
* counts, disable counts, instrumentation ID, tracer and meter state, rate
* limit, error mode, disabled state, logging state, and analyzer bits. When
* DEBUG_OTEL is enabled, the current debug level is also included.
*
* RETURN VALUE
* Returns 1, or 0 on memory allocation failure.
*/
static int flt_otel_cli_parse_status(char **args, char *payload, struct appctx *appctx, void *private)
{
const char *nl = "";
char *msg = NULL;
OTELC_FUNC("%p, \"%s\", %p, %p", args, OTELC_STR_ARG(payload), appctx, private);
FLT_OTEL_ARGS_DUMP();
flt_otel_filters_dump();
(void)memprintf(&msg, " " FLT_OTEL_OPT_NAME " filter status\n" FLT_OTEL_STR_DASH_78 "\n");
(void)memprintf(&msg, "%s library: C++ " OTELCPP_VERSION ", C wrapper %s\n", msg, otelc_version());
#ifdef DEBUG_OTEL
(void)memprintf(&msg, "%s debug level: 0x%02hhx\n", msg, otelc_dbg_level);
#endif
(void)memprintf(&msg, "%s dropped count: %" PRId64 "/%" PRId64 " %" PRIu64 "\n", msg, otelc_processor_dropped_count(0), otelc_processor_dropped_count(1), _HA_ATOMIC_LOAD(&flt_otel_drop_cnt));
FLT_OTEL_PROXIES_LIST_START() {
struct flt_otel_conf_group *grp;
struct flt_otel_conf_scope *scp;
int n_groups = 0, n_scopes = 0;
list_for_each_entry(grp, &(conf->groups), list)
n_groups++;
list_for_each_entry(scp, &(conf->scopes), list)
n_scopes++;
(void)memprintf(&msg, "%s\n%s proxy %s, filter %s\n", msg, nl, px->id, conf->id);
(void)memprintf(&msg, "%s configuration: %s\n", msg, conf->cfg_file);
(void)memprintf(&msg, "%s groups/scopes: %d/%d\n\n", msg, n_groups, n_scopes);
(void)memprintf(&msg, "%s instrumentation %s\n", msg, conf->instr->id);
(void)memprintf(&msg, "%s configuration: %s\n", msg, conf->instr->config);
(void)memprintf(&msg, "%s tracer: %s\n", msg, (conf->instr->tracer != NULL) ? "active" : "not initialized");
(void)memprintf(&msg, "%s meter: %s\n", msg, (conf->instr->meter != NULL) ? "active" : "not initialized");
(void)memprintf(&msg, "%s logger: %s\n", msg, (conf->instr->logger != NULL) ? "active" : "not initialized");
(void)memprintf(&msg, "%s rate limit: %.2f %%\n", msg, FLT_OTEL_U32_FLOAT(_HA_ATOMIC_LOAD(&(conf->instr->rate_limit))));
(void)memprintf(&msg, "%s hard errors: %s\n", msg, FLT_OTEL_STR_FLAG_YN(_HA_ATOMIC_LOAD(&(conf->instr->flag_harderr))));
(void)memprintf(&msg, "%s disabled: %s\n", msg, FLT_OTEL_STR_FLAG_YN(_HA_ATOMIC_LOAD(&(conf->instr->flag_disabled))));
(void)memprintf(&msg, "%s logging: %s\n", msg, FLT_OTEL_CLI_LOGGING_STATE(_HA_ATOMIC_LOAD(&(conf->instr->logging))));
(void)memprintf(&msg, "%s analyzers: %08x", msg, conf->instr->analyzers);
#ifdef FLT_OTEL_USE_COUNTERS
(void)memprintf(&msg, "%s\n\n counters\n", msg);
(void)memprintf(&msg, "%s attached: %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 "\n", msg, conf->cnt.attached[0], conf->cnt.attached[1], conf->cnt.attached[2], conf->cnt.attached[3]);
(void)memprintf(&msg, "%s disabled: %" PRIu64 " %" PRIu64, msg, conf->cnt.disabled[0], conf->cnt.disabled[1]);
#endif
nl = "\n";
} FLT_OTEL_PROXIES_LIST_END();
OTELC_RETURN_INT(flt_otel_cli_set_msg(appctx, NULL, msg));
}
/* CLI command table for the OTel filter. */
static struct cli_kw_list cli_kws = { { }, {
#ifdef DEBUG_OTEL
{ { FLT_OTEL_CLI_CMD, "debug", NULL }, FLT_OTEL_CLI_CMD " debug [level] : set the OTEL filter debug level (default: get current debug level)", flt_otel_cli_parse_debug, NULL, NULL, NULL, ACCESS_LVL_ADMIN },
#endif
{ { FLT_OTEL_CLI_CMD, "disable", NULL }, FLT_OTEL_CLI_CMD " disable : disable the OTEL filter", flt_otel_cli_parse_disabled, NULL, NULL, (void *)1, ACCESS_LVL_ADMIN },
{ { FLT_OTEL_CLI_CMD, "enable", NULL }, FLT_OTEL_CLI_CMD " enable : enable the OTEL filter", flt_otel_cli_parse_disabled, NULL, NULL, (void *)0, ACCESS_LVL_ADMIN },
{ { FLT_OTEL_CLI_CMD, "soft-errors", NULL }, FLT_OTEL_CLI_CMD " soft-errors : disable hard-errors mode", flt_otel_cli_parse_option, NULL, NULL, (void *)0, ACCESS_LVL_ADMIN },
{ { FLT_OTEL_CLI_CMD, "hard-errors", NULL }, FLT_OTEL_CLI_CMD " hard-errors : enable hard-errors mode", flt_otel_cli_parse_option, NULL, NULL, (void *)1, ACCESS_LVL_ADMIN },
{ { FLT_OTEL_CLI_CMD, "logging", NULL }, FLT_OTEL_CLI_CMD " logging [state] : set logging state (default: get current logging state)", flt_otel_cli_parse_logging, NULL, NULL, NULL, ACCESS_LVL_ADMIN },
{ { FLT_OTEL_CLI_CMD, "rate", NULL }, FLT_OTEL_CLI_CMD " rate [value] : set the rate limit (default: get current rate value)", flt_otel_cli_parse_rate, NULL, NULL, NULL, ACCESS_LVL_ADMIN },
{ { FLT_OTEL_CLI_CMD, "status", NULL }, FLT_OTEL_CLI_CMD " status : show the OTEL filter status", flt_otel_cli_parse_status, NULL, NULL, NULL, 0 },
{ /* END */ }
}};
/***
* NAME
* flt_otel_cli_init - CLI keyword registration
*
* SYNOPSIS
* void flt_otel_cli_init(void)
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* Registers the OTel filter CLI keywords with the HAProxy CLI subsystem.
* The keywords include commands for enable/disable, error mode, logging,
* rate limit, status display, and (when DEBUG_OTEL is defined) debug level
* management.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_cli_init(void)
{
OTELC_FUNC("");
/* Register CLI keywords. */
cli_register_kw(&cli_kws);
OTELC_RETURN();
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,885 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
/***
* NAME
* flt_otel_conf_hdr_init - conf_hdr structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_hdr *flt_otel_conf_hdr_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_hdr structure. The <id> string is
* duplicated and stored as the header identifier. If <head> is non-NULL,
* the structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(hdr, id, )
/***
* NAME
* flt_otel_conf_hdr_free - conf_hdr structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_hdr_free(struct flt_otel_conf_hdr **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_hdr structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(hdr, id,
FLT_OTEL_DBG_CONF_HDR("- conf_hdr free ", *ptr, id);
)
/***
* NAME
* flt_otel_conf_str_init - conf_str structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_str *flt_otel_conf_str_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_str structure. The <id> string is
* duplicated and stored as the string value. If <head> is non-NULL, the
* structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(str, str, )
/***
* NAME
* flt_otel_conf_str_free - conf_str structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_str_free(struct flt_otel_conf_str **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_str structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(str, str,
FLT_OTEL_DBG_CONF_HDR("- conf_str free ", *ptr, str);
)
/***
* NAME
* flt_otel_conf_link_init - conf_link structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_link *flt_otel_conf_link_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_link structure for a span link
* reference. The <id> string is duplicated and stored as the linked
* span name. If <head> is non-NULL, the structure is appended to
* the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(link, span, )
/***
* NAME
* flt_otel_conf_link_free - conf_link structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_link_free(struct flt_otel_conf_link **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_link structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(link, span,
FLT_OTEL_DBG_CONF_HDR("- conf_link free ", *ptr, span);
)
/***
* NAME
* flt_otel_conf_ph_init - conf_ph placeholder structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_ph *flt_otel_conf_ph_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_ph (placeholder) structure. The <id>
* string is duplicated and stored as the placeholder identifier. If <head>
* is non-NULL, the structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(ph, id, )
/***
* NAME
* flt_otel_conf_ph_free - conf_ph structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_ph_free(struct flt_otel_conf_ph **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_ph structure and its contents,
* then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(ph, id,
FLT_OTEL_DBG_CONF_HDR("- conf_ph free ", *ptr, id);
)
/***
* NAME
* flt_otel_conf_sample_expr_init - conf_sample_expr structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_sample_expr *flt_otel_conf_sample_expr_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_sample_expr structure. The <id> string is
* duplicated and stored as the expression value. If <head> is non-NULL, the
* structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(sample_expr, fmt_expr, )
/***
* NAME
* flt_otel_conf_sample_expr_free - conf_sample_expr structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_sample_expr_free(struct flt_otel_conf_sample_expr **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_sample_expr structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(sample_expr, fmt_expr,
FLT_OTEL_DBG_CONF_SAMPLE_EXPR("- conf_sample_expr free ", *ptr);
release_sample_expr((*ptr)->expr);
)
/***
* NAME
* flt_otel_conf_sample_init - conf_sample structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_sample *flt_otel_conf_sample_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_sample structure. The <id> string is
* duplicated and stored as the sample key. If <head> is non-NULL, the
* structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(sample, key,
LIST_INIT(&(retptr->exprs));
lf_expr_init(&(retptr->lf_expr));
)
/***
* NAME
* flt_otel_conf_sample_init_ex - extended sample initialization
*
* SYNOPSIS
* struct flt_otel_conf_sample *flt_otel_conf_sample_init_ex(const char **args, int idx, int n, const struct otelc_value *extra, int line, struct list *head, char **err)
*
* ARGUMENTS
* args - configuration line arguments array
* idx - position where sample value starts
* n - maximum number of arguments to concatenate (0 means all)
* extra - optional extra data (event name or status code)
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Creates and initializes a conf_sample structure with extended data. Calls
* flt_otel_conf_sample_init() with <args[idx - 1]> as the sample key to
* create the base structure, copies <extra> data (event name string or status
* code integer), concatenates the remaining arguments into the sample value
* string, and counts the number of sample expressions.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
struct flt_otel_conf_sample *flt_otel_conf_sample_init_ex(const char **args, int idx, int n, const struct otelc_value *extra, int line, struct list *head, char **err)
{
struct flt_otel_conf_sample *retptr = NULL;
OTELC_FUNC("%p, %d, %d, %p, %d, %p, %p:%p", args, idx, n, extra, line, head, OTELC_DPTR_ARGS(err));
OTELC_DBG_VALUE(DEBUG, "extra ", extra);
/* Ensure the sample value is present in the args[] array. */
if (flt_otel_args_count(args) <= idx) {
FLT_OTEL_ERR("'%s' : too few arguments", args[0]);
OTELC_RETURN_PTR(retptr);
}
/* The sample key is located at the idx location of the args[] field. */
retptr = flt_otel_conf_sample_init(args[idx - 1], line, head, err);
if (retptr == NULL)
OTELC_RETURN_PTR(retptr);
if ((extra == NULL) || (extra->u_type == OTELC_VALUE_NULL)) {
/*
* Do nothing - sample extra data is not set or initialized,
* which means it is not used.
*/
}
else if (extra->u_type == OTELC_VALUE_STRING) {
retptr->extra.u_type = OTELC_VALUE_DATA;
retptr->extra.u.value_data = OTELC_STRDUP(extra->u.value_string);
if (retptr->extra.u.value_data == NULL) {
FLT_OTEL_ERR("out of memory");
flt_otel_conf_sample_free(&retptr);
OTELC_RETURN_PTR(retptr);
}
}
else if (extra->u_type == OTELC_VALUE_INT32) {
retptr->extra.u_type = extra->u_type;
retptr->extra.u.value_int32 = extra->u.value_int32;
}
else {
FLT_OTEL_ERR("invalid sample extra data type: %d", extra->u_type);
flt_otel_conf_sample_free(&retptr);
OTELC_RETURN_PTR(retptr);
}
/* The sample value starts in the args[] array after the key. */
retptr->num_exprs = flt_otel_args_concat(args, idx, n, &(retptr->fmt_string));
if (retptr->num_exprs == FLT_OTEL_RET_ERROR) {
FLT_OTEL_ERR("out of memory");
flt_otel_conf_sample_free(&retptr);
OTELC_RETURN_PTR(retptr);
}
FLT_OTEL_DBG_CONF_SAMPLE("- conf_sample init ", retptr);
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_conf_sample_free - conf_sample structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_sample_free(struct flt_otel_conf_sample **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_sample structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(sample, key,
FLT_OTEL_DBG_CONF_SAMPLE("- conf_sample free ", *ptr);
OTELC_SFREE((*ptr)->fmt_string);
if ((*ptr)->extra.u_type == OTELC_VALUE_DATA)
OTELC_SFREE((*ptr)->extra.u.value_data);
FLT_OTEL_LIST_DESTROY(sample_expr, &((*ptr)->exprs));
lf_expr_deinit(&((*ptr)->lf_expr));
)
/***
* NAME
* flt_otel_conf_context_init - conf_context structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_context *flt_otel_conf_context_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_context structure. The <id> string is
* duplicated and stored as the context identifier. If <head> is non-NULL,
* the structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(context, id, )
/***
* NAME
* flt_otel_conf_context_free - conf_context structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_context_free(struct flt_otel_conf_context **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_context structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(context, id,
FLT_OTEL_DBG_CONF_HDR("- conf_context free ", *ptr, id);
)
/***
* NAME
* flt_otel_conf_span_init - conf_span structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_span *flt_otel_conf_span_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_span structure with empty lists for links,
* attributes, events, baggages, and statuses. The <id> string is duplicated
* and stored as the span name. If <head> is non-NULL, the structure is
* appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(span, id,
LIST_INIT(&(retptr->links));
LIST_INIT(&(retptr->attributes));
LIST_INIT(&(retptr->events));
LIST_INIT(&(retptr->baggages));
LIST_INIT(&(retptr->statuses));
)
/***
* NAME
* flt_otel_conf_span_free - conf_span structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_span_free(struct flt_otel_conf_span **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_span structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(span, id,
FLT_OTEL_DBG_CONF_HDR("- conf_span free ", *ptr, id);
OTELC_SFREE((*ptr)->ref_id);
OTELC_SFREE((*ptr)->ctx_id);
FLT_OTEL_LIST_DESTROY(link, &((*ptr)->links));
FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->attributes));
FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->events));
FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->baggages));
FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->statuses));
)
/***
* NAME
* flt_otel_conf_instrument_init - conf_instrument structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_instrument *flt_otel_conf_instrument_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_instrument structure. Sets the instrument
* type and meter index to OTELC_METRIC_INSTRUMENT_UNSET and initializes the
* samples and attributes lists. The <id> string is duplicated and stored as
* the instrument name. If <head> is non-NULL, the structure is appended to
* the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(instrument, id,
retptr->idx = OTELC_METRIC_INSTRUMENT_UNSET;
retptr->type = OTELC_METRIC_INSTRUMENT_UNSET;
retptr->aggr_type = OTELC_METRIC_AGGREGATION_UNSET;
LIST_INIT(&(retptr->samples));
LIST_INIT(&(retptr->attributes));
)
/***
* NAME
* flt_otel_conf_instrument_free - conf_instrument structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_instrument_free(struct flt_otel_conf_instrument **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_instrument structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(instrument, id,
FLT_OTEL_DBG_CONF_INSTRUMENT("- conf_instrument free ", *ptr);
OTELC_SFREE((*ptr)->description);
OTELC_SFREE((*ptr)->unit);
FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->samples));
OTELC_SFREE((*ptr)->bounds);
FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->attributes));
)
/***
* NAME
* flt_otel_conf_log_record_init - conf_log_record structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_log_record *flt_otel_conf_log_record_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_log_record structure. Initializes the
* attributes and sample expressions lists. The <id> string is required by
* the macro but is not used directly; the severity level is stored
* separately. If <head> is non-NULL, the structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(log_record, id,
LIST_INIT(&(retptr->attributes));
LIST_INIT(&(retptr->samples));
)
/***
* NAME
* flt_otel_conf_log_record_free - conf_log_record structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_log_record_free(struct flt_otel_conf_log_record **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_log_record structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(log_record, id,
FLT_OTEL_DBG_CONF_LOG_RECORD("- conf_log_record free ", *ptr);
OTELC_SFREE((*ptr)->event_name);
OTELC_SFREE((*ptr)->span);
FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->attributes));
FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->samples));
)
/***
* NAME
* flt_otel_conf_scope_init - conf_scope structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_scope *flt_otel_conf_scope_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_scope structure with empty lists for ACLs,
* contexts, spans, spans_to_finish, and instruments. The <id> string is
* duplicated and stored as the scope name. If <head> is non-NULL, the
* structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(scope, id,
LIST_INIT(&(retptr->acls));
LIST_INIT(&(retptr->contexts));
LIST_INIT(&(retptr->spans));
LIST_INIT(&(retptr->spans_to_finish));
LIST_INIT(&(retptr->instruments));
LIST_INIT(&(retptr->log_records));
)
/***
* NAME
* flt_otel_conf_scope_free - conf_scope structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_scope_free(struct flt_otel_conf_scope **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_scope structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(scope, id,
struct acl *acl;
struct acl *aclback;
FLT_OTEL_DBG_CONF_SCOPE("- conf_scope free ", *ptr);
list_for_each_entry_safe(acl, aclback, &((*ptr)->acls), list) {
prune_acl(acl);
FLT_OTEL_LIST_DEL(&(acl->list));
OTELC_SFREE(acl);
}
free_acl_cond((*ptr)->cond);
FLT_OTEL_LIST_DESTROY(context, &((*ptr)->contexts));
FLT_OTEL_LIST_DESTROY(span, &((*ptr)->spans));
FLT_OTEL_LIST_DESTROY(str, &((*ptr)->spans_to_finish));
FLT_OTEL_LIST_DESTROY(instrument, &((*ptr)->instruments));
FLT_OTEL_LIST_DESTROY(log_record, &((*ptr)->log_records));
)
/***
* NAME
* flt_otel_conf_group_init - conf_group structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_group *flt_otel_conf_group_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_group structure with an empty placeholder
* scope list. The <id> string is duplicated and stored as the group name.
* If <head> is non-NULL, the structure is appended to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(group, id,
LIST_INIT(&(retptr->ph_scopes));
)
/***
* NAME
* flt_otel_conf_group_free - conf_group structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_group_free(struct flt_otel_conf_group **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_group structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(group, id,
FLT_OTEL_DBG_CONF_GROUP("- conf_group free ", *ptr);
FLT_OTEL_LIST_DESTROY(ph_scope, &((*ptr)->ph_scopes));
)
/***
* NAME
* flt_otel_conf_instr_init - conf_instr structure allocation
*
* SYNOPSIS
* struct flt_otel_conf_instr *flt_otel_conf_instr_init(const char *id, int line, struct list *head, char **err)
*
* ARGUMENTS
* id - identifier string to duplicate
* line - configuration file line number
* head - list to append to (or NULL)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a conf_instr (instrumentation) structure. Sets
* the default rate limit to 100%, initializes the proxy_log for logger
* support, and creates empty lists for ACLs, placeholder groups, and
* placeholder scopes. The <id> string is duplicated and stored as the
* instrumentation name. If <head> is non-NULL, the structure is appended
* to the list.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
FLT_OTEL_CONF_FUNC_INIT(instr, id,
retptr->rate_limit = FLT_OTEL_FLOAT_U32(100.0);
init_new_proxy(&(retptr->proxy_log));
LIST_INIT(&(retptr->acls));
LIST_INIT(&(retptr->ph_groups));
LIST_INIT(&(retptr->ph_scopes));
)
/***
* NAME
* flt_otel_conf_instr_free - conf_instr structure deallocation
*
* SYNOPSIS
* void flt_otel_conf_instr_free(struct flt_otel_conf_instr **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf_instr structure and its
* contents, then removes it from the list of structures of that type.
*
* RETURN VALUE
* This function does not return a value.
*/
FLT_OTEL_CONF_FUNC_FREE(instr, id,
struct acl *acl;
struct acl *aclback;
struct logger *logger;
struct logger *loggerback;
FLT_OTEL_DBG_CONF_INSTR("- conf_instr free ", *ptr);
OTELC_SFREE((*ptr)->config);
OTELC_DBG(NOTICE, "- deleting acls list %s", flt_otel_list_dump(&((*ptr)->acls)));
list_for_each_entry_safe(acl, aclback, &((*ptr)->acls), list) {
prune_acl(acl);
FLT_OTEL_LIST_DEL(&(acl->list));
OTELC_SFREE(acl);
}
OTELC_DBG(NOTICE, "- deleting proxy_log.loggers list %s", flt_otel_list_dump(&((*ptr)->proxy_log.loggers)));
list_for_each_entry_safe(logger, loggerback, &((*ptr)->proxy_log.loggers), list) {
LIST_DELETE(&(logger->list));
ha_free(&logger);
}
FLT_OTEL_LIST_DESTROY(ph_group, &((*ptr)->ph_groups));
FLT_OTEL_LIST_DESTROY(ph_scope, &((*ptr)->ph_scopes));
)
/***
* NAME
* flt_otel_conf_init - top-level filter configuration allocation
*
* SYNOPSIS
* struct flt_otel_conf *flt_otel_conf_init(struct proxy *px)
*
* ARGUMENTS
* px - proxy instance to associate with
*
* DESCRIPTION
* Allocates and initializes the top-level flt_otel_conf structure. Stores
* the <px> proxy reference and creates empty group and scope lists.
*
* RETURN VALUE
* Returns a pointer to the initialized structure, or NULL on failure.
*/
struct flt_otel_conf *flt_otel_conf_init(struct proxy *px)
{
struct flt_otel_conf *retptr;
OTELC_FUNC("%p", px);
retptr = OTELC_CALLOC(1, sizeof(*retptr));
if (retptr == NULL)
OTELC_RETURN_PTR(retptr);
retptr->proxy = px;
LIST_INIT(&(retptr->groups));
LIST_INIT(&(retptr->scopes));
LIST_INIT(&(retptr->smp_args));
FLT_OTEL_DBG_CONF("- conf init ", retptr);
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_conf_free - top-level filter configuration deallocation
*
* SYNOPSIS
* void flt_otel_conf_free(struct flt_otel_conf **ptr)
*
* ARGUMENTS
* ptr - a pointer to the address of a structure
*
* DESCRIPTION
* Deallocates memory used by the flt_otel_conf structure and its contents.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_conf_free(struct flt_otel_conf **ptr)
{
struct arg_list *cur, *back;
OTELC_FUNC("%p:%p", OTELC_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
OTELC_RETURN();
FLT_OTEL_DBG_CONF("- conf free ", *ptr);
OTELC_SFREE((*ptr)->id);
OTELC_SFREE((*ptr)->cfg_file);
flt_otel_conf_instr_free(&((*ptr)->instr));
FLT_OTEL_LIST_DESTROY(group, &((*ptr)->groups));
FLT_OTEL_LIST_DESTROY(scope, &((*ptr)->scopes));
/* Free any unresolved OTEL sample fetch args (error path). */
list_for_each_entry_safe(cur, back, &((*ptr)->smp_args), list) {
LIST_DELETE(&(cur->list));
ha_free(&cur);
}
OTELC_SFREE_CLEAR(*ptr);
OTELC_RETURN();
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,849 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
/* Event data table built from the X-macro list. */
#define FLT_OTEL_EVENT_DEF(a,b,c,d,e,f) { AN_##b##_##a, OTELC_STRINGIFY_ARG(AN_##b##_##a), SMP_OPT_DIR_##b, SMP_VAL_FE_##c, SMP_VAL_BE_##d, e, f },
const struct flt_otel_event_data flt_otel_event_data[FLT_OTEL_EVENT_MAX] = { FLT_OTEL_EVENT_DEFINES };
#undef FLT_OTEL_EVENT_DEF
/***
* NAME
* flt_otel_scope_run_instrument_record - metric instrument value recorder
*
* SYNOPSIS
* static int flt_otel_scope_run_instrument_record(struct stream *s, uint dir, struct otelc_meter *meter, struct flt_otel_conf_instrument *instr_ref, struct flt_otel_conf_instrument *instr, char **err)
*
* ARGUMENTS
* s - the stream providing the sample context
* dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
* meter - the OTel meter instance
* instr_ref - the create-form instrument providing samples and meter index
* instr - the update-form instrument providing per-scope attributes
* err - indirect pointer to error message string
*
* DESCRIPTION
* Evaluates sample expressions from a create-form instrument and records
* the resulting value via the <meter> API. Each expression is evaluated
* with sample_process(), converted to an otelc_value via
* flt_otel_sample_to_value(), and recorded via
* <meter>->update_instrument_kv_n().
*
* RETURN VALUE
* Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
*/
static int flt_otel_scope_run_instrument_record(struct stream *s, uint dir, struct otelc_meter *meter, struct flt_otel_conf_instrument *instr_ref, struct flt_otel_conf_instrument *instr, char **err)
{
struct flt_otel_conf_sample *sample;
struct flt_otel_conf_sample_expr *expr;
struct sample smp;
struct otelc_value value;
struct flt_otel_scope_data_kv instr_attr;
int retval = FLT_OTEL_RET_OK;
OTELC_FUNC("%p, %u, %p, %p, %p, %p:%p", s, dir, meter, instr_ref, instr, OTELC_DPTR_ARGS(err));
/* Evaluate instrument attributes from sample expressions. */
(void)memset(&instr_attr, 0, sizeof(instr_attr));
list_for_each_entry(sample, &(instr->attributes), list) {
struct otelc_value attr_value;
OTELC_DBG(DEBUG, "adding instrument attribute '%s' -> '%s'", sample->key, sample->fmt_string);
if (flt_otel_sample_eval(s, dir, sample, true, &attr_value, err) == FLT_OTEL_RET_ERROR) {
retval = FLT_OTEL_RET_ERROR;
continue;
}
if (flt_otel_sample_add_kv(&instr_attr, sample->key, &attr_value) == FLT_OTEL_RET_ERROR) {
if (attr_value.u_type == OTELC_VALUE_DATA)
OTELC_SFREE(attr_value.u.value_data);
retval = FLT_OTEL_RET_ERROR;
}
}
/* The samples list always contains exactly one entry. */
sample = LIST_NEXT(&(instr_ref->samples), struct flt_otel_conf_sample *, list);
(void)memset(&smp, 0, sizeof(smp));
if (sample->lf_used) {
/*
* Log-format path: evaluate into a temporary buffer and present
* the result as a string sample.
*/
smp.data.u.str.area = OTELC_CALLOC(1, global.tune.bufsize);
if (smp.data.u.str.area == NULL) {
FLT_OTEL_ERR("out of memory");
otelc_kv_destroy(&(instr_attr.attr), instr_attr.cnt);
OTELC_RETURN_INT(FLT_OTEL_RET_ERROR);
}
smp.data.type = SMP_T_STR;
smp.data.u.str.data = build_logline(s, smp.data.u.str.area, global.tune.bufsize, &(sample->lf_expr));
} else {
/* The expressions list always contains exactly one entry. */
expr = LIST_NEXT(&(sample->exprs), struct flt_otel_conf_sample_expr *, list);
FLT_OTEL_DBG_CONF_SAMPLE_EXPR("sample expression ", expr);
if (sample_process(s->be, s->sess, s, dir | SMP_OPT_FINAL, expr->expr, &smp) == NULL) {
OTELC_DBG(NOTICE, "WARNING: failed to fetch '%s'", expr->fmt_expr);
retval = FLT_OTEL_RET_ERROR;
}
}
if (retval == FLT_OTEL_RET_ERROR) {
/* Do nothing. */
}
else if (flt_otel_sample_to_value(sample->key, &(smp.data), &value, err) == FLT_OTEL_RET_ERROR) {
if (value.u_type == OTELC_VALUE_DATA)
OTELC_SFREE(value.u.value_data);
retval = FLT_OTEL_RET_ERROR;
}
else {
OTELC_DBG_VALUE(DEBUG, "value ", &value);
/*
* Metric instruments expect numeric values (INT64 or DOUBLE).
* Reject OTELC_VALUE_DATA since the meter cannot interpret
* arbitrary string data as a numeric measurement.
*/
if (value.u_type == OTELC_VALUE_DATA) {
OTELC_DBG(NOTICE, "WARNING: non-numeric value type for instrument '%s'", instr_ref->id);
if (otelc_value_strtonum(&value, OTELC_VALUE_INT64) == OTELC_RET_ERROR) {
OTELC_SFREE(value.u.value_data);
retval = FLT_OTEL_RET_ERROR;
}
}
if (retval != FLT_OTEL_RET_ERROR)
if (OTELC_OPS(meter, update_instrument_kv_n, HA_ATOMIC_LOAD(&(instr_ref->idx)), &value, instr_attr.attr, instr_attr.cnt) == OTELC_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
}
otelc_kv_destroy(&(instr_attr.attr), instr_attr.cnt);
if (sample->lf_used)
OTELC_SFREE(smp.data.u.str.area);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_scope_run_instrument - metric instrument processor
*
* SYNOPSIS
* static int flt_otel_scope_run_instrument(struct stream *s, uint dir, struct flt_otel_conf_scope *scope, struct otelc_meter *meter, char **err)
*
* ARGUMENTS
* s - the stream providing the sample context
* dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
* scope - the scope configuration containing the instrument list
* meter - the OTel meter instance
* err - indirect pointer to error message string
*
* DESCRIPTION
* Processes all metric instruments configured in <scope>. Runs in two
* passes: the first pass lazily creates create-form instruments via <meter>
* on first use, using HA_ATOMIC_CAS on the instrument index to guarantee
* thread-safe one-time initialization. The second pass iterates over
* update-form instruments and records measurements via
* flt_otel_scope_run_instrument_record(). Instruments whose index is still
* negative (UNUSED or PENDING) are skipped, so that a concurrent creation by
* another thread does not cause an invalid <meter> access.
*
* RETURN VALUE
* Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
*/
static int flt_otel_scope_run_instrument(struct stream *s, uint dir, struct flt_otel_conf_scope *scope, struct otelc_meter *meter, char **err)
{
struct flt_otel_conf_instrument *conf_instr;
int retval = FLT_OTEL_RET_OK;
OTELC_FUNC("%p, %u, %p, %p, %p:%p", s, dir, scope, meter, OTELC_DPTR_ARGS(err));
list_for_each_entry(conf_instr, &(scope->instruments), list) {
if (conf_instr->type == OTELC_METRIC_INSTRUMENT_UPDATE) {
/* Do nothing. */
}
else if (HA_ATOMIC_LOAD(&(conf_instr->idx)) == OTELC_METRIC_INSTRUMENT_UNSET) {
int64_t expected = OTELC_METRIC_INSTRUMENT_UNSET;
int rc;
OTELC_DBG(DEBUG, "run instrument '%s' -> '%s'", scope->id, conf_instr->id);
FLT_OTEL_DBG_CONF_INSTRUMENT("", conf_instr);
/*
* Create form: use this instrument directly. Lazily
* create the instrument on first use. Use CAS to
* ensure only one thread performs the creation in a
* multi-threaded environment.
*/
if (!HA_ATOMIC_CAS(&(conf_instr->idx), &expected, OTELC_METRIC_INSTRUMENT_PENDING))
continue;
/*
* The view must be created before the instrument,
* otherwise bucket boundaries cannot be set.
*/
if ((conf_instr->bounds != NULL) && (conf_instr->bounds_num > 0))
if (OTELC_OPS(meter, add_view, conf_instr->id, conf_instr->description, conf_instr->id, conf_instr->unit, conf_instr->type, conf_instr->aggr_type, conf_instr->bounds, conf_instr->bounds_num) == OTELC_RET_ERROR)
OTELC_DBG(NOTICE, "WARNING: failed to add view for instrument '%s'", conf_instr->id);
rc = OTELC_OPS(meter, create_instrument, conf_instr->id, conf_instr->description, conf_instr->unit, conf_instr->type, NULL);
if (rc == OTELC_RET_ERROR) {
OTELC_DBG(NOTICE, "WARNING: failed to create instrument '%s'", conf_instr->id);
HA_ATOMIC_STORE(&(conf_instr->idx), OTELC_METRIC_INSTRUMENT_UNSET);
retval = FLT_OTEL_RET_ERROR;
continue;
} else {
HA_ATOMIC_STORE(&(conf_instr->idx), rc);
}
}
}
list_for_each_entry(conf_instr, &(scope->instruments), list)
if (conf_instr->type == OTELC_METRIC_INSTRUMENT_UPDATE) {
struct flt_otel_conf_instrument *instr = conf_instr->ref;
OTELC_DBG(DEBUG, "run instrument '%s' -> '%s'", scope->id, conf_instr->id);
FLT_OTEL_DBG_CONF_INSTRUMENT("", conf_instr);
/*
* Update form: record a measurement using an existing
* create-form instrument.
*/
if (instr == NULL) {
OTELC_DBG(NOTICE, "WARNING: invalid reference instrument '%s'", conf_instr->id);
retval = FLT_OTEL_RET_ERROR;
}
else if (HA_ATOMIC_LOAD(&(instr->idx)) < 0) {
OTELC_DBG(NOTICE, "WARNING: instrument '%s' not yet created, skipping", instr->id);
}
else if (flt_otel_scope_run_instrument_record(s, dir, meter, instr, conf_instr, err) == FLT_OTEL_RET_ERROR) {
retval = FLT_OTEL_RET_ERROR;
}
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_scope_run_log_record - log record emitter
*
* SYNOPSIS
* static int flt_otel_scope_run_log_record(struct stream *s, struct filter *f, uint dir, struct flt_otel_conf_scope *scope, struct otelc_logger *logger, const struct timespec *ts, char **err)
*
* ARGUMENTS
* s - the stream providing the sample context
* f - the filter instance
* dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
* scope - the scope configuration containing the log record list
* logger - the OTel logger instance
* ts - the wall-clock timestamp for the log record
* err - indirect pointer to error message string
*
* DESCRIPTION
* Processes all log records configured in <scope>. For each record, checks
* whether the logger is enabled for the configured severity, evaluates the
* sample expressions into a body string, resolves the optional span reference
* against the runtime context, and emits the log record via the logger's
* log_span operation.
*
* RETURN VALUE
* Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
*/
static int flt_otel_scope_run_log_record(struct stream *s, struct filter *f, uint dir, struct flt_otel_conf_scope *scope, struct otelc_logger *logger, const struct timespec *ts, char **err)
{
struct flt_otel_conf_log_record *conf_log;
int retval = FLT_OTEL_RET_OK;
OTELC_FUNC("%p, %p, %u, %p, %p, %p, %p:%p", s, f, dir, scope, logger, ts, OTELC_DPTR_ARGS(err));
list_for_each_entry(conf_log, &(scope->log_records), list) {
struct flt_otel_conf_sample *sample;
struct flt_otel_conf_sample_expr *expr;
struct sample smp;
struct otelc_span *otel_span = NULL;
struct flt_otel_scope_data_kv log_attr;
struct buffer buffer;
int rc;
OTELC_DBG(DEBUG, "run log-record '%s' -> '%s'", scope->id, conf_log->id);
/* Skip if the logger is not enabled for this severity. */
if (OTELC_OPS(logger, enabled, conf_log->severity) == 0)
continue;
/* Evaluate log record attributes from sample expressions. */
(void)memset(&log_attr, 0, sizeof(log_attr));
list_for_each_entry(sample, &(conf_log->attributes), list) {
struct otelc_value attr_value;
OTELC_DBG(DEBUG, "adding log-record attribute '%s' -> '%s'", sample->key, sample->fmt_string);
if (flt_otel_sample_eval(s, dir, sample, true, &attr_value, err) == FLT_OTEL_RET_ERROR) {
retval = FLT_OTEL_RET_ERROR;
continue;
}
if (flt_otel_sample_add_kv(&log_attr, sample->key, &attr_value) == FLT_OTEL_RET_ERROR) {
if (attr_value.u_type == OTELC_VALUE_DATA)
OTELC_SFREE(attr_value.u.value_data);
retval = FLT_OTEL_RET_ERROR;
}
}
/* The samples list has exactly one entry. */
sample = LIST_NEXT(&(conf_log->samples), typeof(sample), list);
(void)memset(&buffer, 0, sizeof(buffer));
if (sample->lf_used) {
/*
* Log-format path: evaluate the log-format expression
* into a dynamically allocated buffer.
*/
chunk_init(&buffer, OTELC_CALLOC(1, global.tune.bufsize), global.tune.bufsize);
if (buffer.area != NULL)
buffer.data = build_logline(s, buffer.area, buffer.size, &(sample->lf_expr));
} else {
/*
* Bare sample expression path: evaluate each expression
* and concatenate the results.
*/
list_for_each_entry(expr, &(sample->exprs), list) {
(void)memset(&smp, 0, sizeof(smp));
if (sample_process(s->be, s->sess, s, dir | SMP_OPT_FINAL, expr->expr, &smp) == NULL) {
OTELC_DBG(NOTICE, "WARNING: failed to fetch '%s'", expr->fmt_expr);
retval = FLT_OTEL_RET_ERROR;
break;
}
if (buffer.area == NULL) {
chunk_init(&buffer, OTELC_CALLOC(1, global.tune.bufsize), global.tune.bufsize);
if (buffer.area == NULL)
break;
}
rc = flt_otel_sample_to_str(&(smp.data), buffer.area + buffer.data, buffer.size - buffer.data, err);
if (rc == FLT_OTEL_RET_ERROR) {
retval = FLT_OTEL_RET_ERROR;
break;
}
buffer.data += rc;
}
}
if (buffer.area == NULL) {
FLT_OTEL_ERR("out of memory");
retval = FLT_OTEL_RET_ERROR;
otelc_kv_destroy(&(log_attr.attr), log_attr.cnt);
continue;
}
/*
* If the log record references a span, resolve it against the
* runtime context. A missing span is not fatal -- the log
* record is emitted without span correlation.
*/
if (conf_log->span != NULL) {
struct flt_otel_runtime_context *rt_ctx = FLT_OTEL_RT_CTX(f->ctx);
struct flt_otel_scope_span *sc_span;
list_for_each_entry(sc_span, &(rt_ctx->spans), list)
if (strcmp(sc_span->id, conf_log->span) == 0) {
otel_span = sc_span->span;
break;
}
if (otel_span == NULL)
OTELC_DBG(NOTICE, "WARNING: cannot find span '%s' for log-record", conf_log->span);
}
if (OTELC_OPS(logger, log_span, conf_log->severity, conf_log->event_id, conf_log->event_name, otel_span, ts, log_attr.attr, log_attr.cnt, "%s", buffer.area) == OTELC_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
otelc_kv_destroy(&(log_attr.attr), log_attr.cnt);
OTELC_SFREE(buffer.area);
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_scope_run_span - single span execution
*
* SYNOPSIS
* static int flt_otel_scope_run_span(struct stream *s, struct filter *f, struct channel *chn, uint dir, struct flt_otel_scope_span *span, struct flt_otel_scope_data *data, const struct flt_otel_conf_span *conf_span, const struct timespec *ts_steady, const struct timespec *ts_system, char **err)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* chn - the channel used for HTTP header injection
* dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
* span - the runtime scope span to execute
* data - the evaluated scope data (attributes, events, links, status)
* conf_span - the span configuration
* ts_steady - the monotonic timestamp for span creation
* ts_system - the wall-clock timestamp for span events
* err - indirect pointer to error message string
*
* DESCRIPTION
* Executes a single span: creates the OTel span on first call via the tracer,
* adds links, baggage, attributes, events and status from <data>, then
* injects the span context into HTTP headers or HAProxy variables if
* configured in <conf_span>.
*
* RETURN VALUE
* Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
*/
static int flt_otel_scope_run_span(struct stream *s, struct filter *f, struct channel *chn, uint dir, struct flt_otel_scope_span *span, struct flt_otel_scope_data *data, const struct flt_otel_conf_span *conf_span, const struct timespec *ts_steady, const struct timespec *ts_system, char **err)
{
struct flt_otel_conf *conf = FLT_OTEL_CONF(f);
int retval = FLT_OTEL_RET_OK;
OTELC_FUNC("%p, %p, %p, %u, %p, %p, %p, %p, %p, %p:%p", s, f, chn, dir, span, data, conf_span, ts_steady, ts_system, OTELC_DPTR_ARGS(err));
if (span == NULL)
OTELC_RETURN_INT(retval);
/* Create the OTel span on first invocation. */
if (span->span == NULL) {
span->span = OTELC_OPS(conf->instr->tracer, start_span_with_options, span->id, span->ref_span, span->ref_ctx, ts_steady, ts_system, OTELC_SPAN_KIND_SERVER, NULL, 0);
if (span->span == NULL)
OTELC_RETURN_INT(FLT_OTEL_RET_ERROR);
}
/* Add all resolved span links to the current span. */
if (!LIST_ISEMPTY(&(data->links))) {
struct flt_otel_scope_data_link *link;
list_for_each_entry(link, &(data->links), list) {
OTELC_DBG(DEBUG, "adding link %p %p", link->span, link->context);
if (OTELC_OPS(span->span, add_link, link->span, link->context, NULL, 0) == -1)
retval = FLT_OTEL_RET_ERROR;
}
}
/* Set baggage key-value pairs on the span. */
if (data->baggage.attr != NULL)
if (OTELC_OPS(span->span, set_baggage_kv_n, data->baggage.attr, data->baggage.cnt) == -1)
retval = FLT_OTEL_RET_ERROR;
/* Set span attributes. */
if (data->attributes.attr != NULL)
if (OTELC_OPS(span->span, set_attribute_kv_n, data->attributes.attr, data->attributes.cnt) == -1)
retval = FLT_OTEL_RET_ERROR;
/* Add span events in reverse order. */
if (!LIST_ISEMPTY(&(data->events))) {
struct flt_otel_scope_data_event *event;
list_for_each_entry_rev(event, &(data->events), list)
if (OTELC_OPS(span->span, add_event_kv_n, event->name, ts_system, event->attr, event->cnt) == -1)
retval = FLT_OTEL_RET_ERROR;
}
/* Set span status code and description. */
if (data->status.description != NULL)
if (OTELC_OPS(span->span, set_status, data->status.code, data->status.description) == -1)
retval = FLT_OTEL_RET_ERROR;
/* Inject span context into HTTP headers and variables. */
if (conf_span->ctx_id != NULL) {
struct otelc_http_headers_writer writer;
struct otelc_text_map *text_map = NULL;
if (flt_otel_inject_http_headers(span->span, &writer) != FLT_OTEL_RET_ERROR) {
int i = 0;
if (conf_span->ctx_flags & (FLT_OTEL_CTX_USE_VARS | FLT_OTEL_CTX_USE_HEADERS)) {
for (text_map = &(writer.text_map); i < text_map->count; i++) {
#ifdef USE_OTEL_VARS
if (!(conf_span->ctx_flags & FLT_OTEL_CTX_USE_VARS))
/* Do nothing. */;
else if (flt_otel_var_register(FLT_OTEL_VARS_SCOPE, conf_span->ctx_id, text_map->key[i], err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
else if (flt_otel_var_set(s, FLT_OTEL_VARS_SCOPE, conf_span->ctx_id, text_map->key[i], text_map->value[i], dir, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
#endif
if (!(conf_span->ctx_flags & FLT_OTEL_CTX_USE_HEADERS))
/* Do nothing. */;
else if (flt_otel_http_header_set(chn, conf_span->ctx_id, text_map->key[i], text_map->value[i], err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
}
}
otelc_text_map_destroy(&text_map);
}
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_scope_run - scope execution engine
*
* SYNOPSIS
* int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_otel_conf_scope *conf_scope, const struct timespec *ts_steady, const struct timespec *ts_system, uint dir, char **err)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* chn - the channel for context extraction and injection
* conf_scope - the scope configuration to execute
* ts_steady - the monotonic timestamp, or NULL to use current time
* ts_system - the wall-clock timestamp, or NULL to use current time
* dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Executes a complete scope: evaluates ACL conditions, extracts contexts
* from HTTP headers or HAProxy variables, iterates over configured spans
* (resolving links, evaluating sample expressions for attributes, events,
* baggage and status), calls flt_otel_scope_run_span() for each, processes
* metric instruments, emits log records, then marks and finishes completed
* spans.
*
* RETURN VALUE
* Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
*/
int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_otel_conf_scope *conf_scope, const struct timespec *ts_steady, const struct timespec *ts_system, uint dir, char **err)
{
struct flt_otel_conf *conf = FLT_OTEL_CONF(f);
struct flt_otel_conf_context *conf_ctx;
struct flt_otel_conf_span *conf_span;
struct flt_otel_conf_str *span_to_finish;
struct timespec ts_now_steady, ts_now_system;
int retval = FLT_OTEL_RET_OK;
OTELC_FUNC("%p, %p, %p, %p, %p, %p, %u, %p:%p", s, f, chn, conf_scope, ts_steady, ts_system, dir, OTELC_DPTR_ARGS(err));
OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s)", flt_otel_chn_label(chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s));
OTELC_DBG(DEBUG, "run scope '%s' %d", conf_scope->id, conf_scope->event);
FLT_OTEL_DBG_CONF_SCOPE("run scope ", conf_scope);
if (ts_steady == NULL) {
(void)clock_gettime(CLOCK_MONOTONIC, &ts_now_steady);
ts_steady = &ts_now_steady;
}
if (ts_system == NULL) {
(void)clock_gettime(CLOCK_REALTIME, &ts_now_system);
ts_system = &ts_now_system;
}
/* Evaluate the scope's ACL condition; skip this scope on mismatch. */
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;
OTELC_DBG(DEBUG, "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 == 0) {
list_for_each_entry(conf_span, &(conf_scope->spans), list)
if (conf_span->flag_root) {
OTELC_DBG(LOG, "session disabled");
FLT_OTEL_RT_CTX(f->ctx)->flag_disabled = 1;
#ifdef FLT_OTEL_USE_COUNTERS
_HA_ATOMIC_ADD(conf->cnt.disabled + 0, 1);
#endif
break;
}
OTELC_RETURN_INT(retval);
}
}
/* Extract and initialize OpenTelemetry propagation contexts. */
list_for_each_entry(conf_ctx, &(conf_scope->contexts), list) {
struct otelc_text_map *text_map = NULL;
OTELC_DBG(DEBUG, "run context '%s' -> '%s'", conf_scope->id, conf_ctx->id);
FLT_OTEL_DBG_CONF_CONTEXT("run context ", conf_ctx);
/*
* The OpenTelemetry context is read from the HTTP header
* or from HAProxy variables.
*/
if (conf_ctx->flags & FLT_OTEL_CTX_USE_HEADERS)
text_map = flt_otel_http_headers_get(chn, conf_ctx->id, conf_ctx->id_len, err);
#ifdef USE_OTEL_VARS
else
text_map = flt_otel_vars_get(s, FLT_OTEL_VARS_SCOPE, conf_ctx->id, dir, err);
#endif
if (text_map != NULL) {
if (flt_otel_scope_context_init(f->ctx, conf->instr->tracer, conf_ctx->id, conf_ctx->id_len, text_map, dir, err) == NULL)
retval = FLT_OTEL_RET_ERROR;
otelc_text_map_destroy(&text_map);
} else {
retval = FLT_OTEL_RET_ERROR;
}
}
/* Process configured spans: resolve links and collect samples. */
list_for_each_entry(conf_span, &(conf_scope->spans), list) {
struct flt_otel_scope_data data;
struct flt_otel_scope_span *span;
struct flt_otel_conf_sample *sample;
OTELC_DBG(DEBUG, "run span '%s' -> '%s'", conf_scope->id, conf_span->id);
FLT_OTEL_DBG_CONF_SPAN("run span ", conf_span);
flt_otel_scope_data_init(&data);
span = flt_otel_scope_span_init(f->ctx, conf_span->id, conf_span->id_len, conf_span->ref_id, conf_span->ref_id_len, dir, err);
if (span == NULL)
retval = FLT_OTEL_RET_ERROR;
/*
* Resolve configured span links against the runtime context.
* Each link name is looked up first in the active spans, then
* in the extracted contexts.
*/
if (!LIST_ISEMPTY(&(conf_span->links))) {
struct flt_otel_runtime_context *rt_ctx = FLT_OTEL_RT_CTX(f->ctx);
struct flt_otel_conf_link *conf_link;
list_for_each_entry(conf_link, &(conf_span->links), list) {
struct flt_otel_scope_data_link *data_link;
struct otelc_span *link_span = NULL;
struct otelc_span_context *link_ctx = NULL;
struct flt_otel_scope_span *sc_span;
struct flt_otel_scope_context *sc_ctx;
/* Try to find a matching span first. */
list_for_each_entry(sc_span, &(rt_ctx->spans), list)
if (FLT_OTEL_CONF_STR_CMP(sc_span->id, conf_link->span)) {
link_span = sc_span->span;
break;
}
/* If no span found, try to find a matching context. */
if (link_span == NULL) {
list_for_each_entry(sc_ctx, &(rt_ctx->contexts), list)
if (FLT_OTEL_CONF_STR_CMP(sc_ctx->id, conf_link->span)) {
link_ctx = sc_ctx->context;
break;
}
}
if ((link_span == NULL) && (link_ctx == NULL)) {
OTELC_DBG(NOTICE, "WARNING: cannot find linked span/context '%s'", conf_link->span);
continue;
}
data_link = OTELC_CALLOC(1, sizeof(*data_link));
if (data_link == NULL) {
retval = FLT_OTEL_RET_ERROR;
break;
}
data_link->span = link_span;
data_link->context = link_ctx;
LIST_APPEND(&(data.links), &(data_link->list));
OTELC_DBG(DEBUG, "resolved link '%s' -> %p %p", conf_link->span, link_span, link_ctx);
}
}
list_for_each_entry(sample, &(conf_span->attributes), list) {
OTELC_DBG(DEBUG, "adding attribute '%s' -> '%s'", sample->key, sample->fmt_string);
if (flt_otel_sample_add(s, dir, sample, &data, FLT_OTEL_EVENT_SAMPLE_ATTRIBUTE, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
}
list_for_each_entry(sample, &(conf_span->events), list) {
OTELC_DBG(DEBUG, "adding event '%s' -> '%s'", sample->key, sample->fmt_string);
if (flt_otel_sample_add(s, dir, sample, &data, FLT_OTEL_EVENT_SAMPLE_EVENT, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
}
list_for_each_entry(sample, &(conf_span->baggages), list) {
OTELC_DBG(DEBUG, "adding baggage '%s' -> '%s'", sample->key, sample->fmt_string);
if (flt_otel_sample_add(s, dir, sample, &data, FLT_OTEL_EVENT_SAMPLE_BAGGAGE, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
}
/*
* Regardless of the use of the list, only one status per event
* is allowed.
*/
list_for_each_entry(sample, &(conf_span->statuses), list) {
OTELC_DBG(DEBUG, "adding status '%s' -> '%s'", sample->key, sample->fmt_string);
if (flt_otel_sample_add(s, dir, sample, &data, FLT_OTEL_EVENT_SAMPLE_STATUS, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
}
/* Attempt to run the span regardless of earlier errors. */
if (span != NULL)
if (flt_otel_scope_run_span(s, f, chn, dir, span, &data, conf_span, ts_steady, ts_system, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
flt_otel_scope_data_free(&data);
}
/* Process metric instruments. */
if (!LIST_ISEMPTY(&(conf_scope->instruments)))
if (flt_otel_scope_run_instrument(s, dir, conf_scope, conf->instr->meter, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
/* Emit log records. */
if (!LIST_ISEMPTY(&(conf_scope->log_records)))
if (flt_otel_scope_run_log_record(s, f, dir, conf_scope, conf->instr->logger, ts_system, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
/* Mark the configured spans for finishing and clean up. */
list_for_each_entry(span_to_finish, &(conf_scope->spans_to_finish), list)
if (flt_otel_scope_finish_mark(f->ctx, span_to_finish->str, span_to_finish->str_len) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
flt_otel_scope_finish_marked(f->ctx, ts_steady);
flt_otel_scope_free_unused(f->ctx, chn);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_event_run - top-level event dispatcher
*
* SYNOPSIS
* int flt_otel_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* chn - the channel being analyzed
* event - the event index (FLT_OTEL_EVENT_*)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Top-level event dispatcher called from filter callbacks. It iterates over
* all scopes matching the <event> index and calls flt_otel_scope_run() for
* each. All spans within a single event share the same monotonic and
* wall-clock timestamps.
*
* RETURN VALUE
* Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
*/
int flt_otel_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err)
{
struct flt_otel_conf *conf = FLT_OTEL_CONF(f);
struct flt_otel_conf_scope *conf_scope;
struct timespec ts_steady, ts_system;
int retval = FLT_OTEL_RET_OK;
OTELC_FUNC("%p, %p, %p, %d, %p:%p", s, f, chn, event, OTELC_DPTR_ARGS(err));
OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s)", flt_otel_chn_label(chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s));
OTELC_DBG(DEBUG, "run event '%s' %d %s", flt_otel_event_data[event].name, event, flt_otel_event_data[event].an_name);
#ifdef DEBUG_OTEL
_HA_ATOMIC_ADD(conf->cnt.event[event].htx + ((chn == NULL) ? 1 : (htx_is_empty(htxbuf(&(chn->buf))) ? 1 : 0)), 1);
#endif
FLT_OTEL_RT_CTX(f->ctx)->analyzers |= flt_otel_event_data[event].an_bit;
/* All spans should be created/completed at the same time. */
(void)clock_gettime(CLOCK_MONOTONIC, &ts_steady);
(void)clock_gettime(CLOCK_REALTIME, &ts_system);
/*
* 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)
OTELC_DBG(DEBUG, "scope '%s' %d not used", conf_scope->id, conf_scope->event);
else if (flt_otel_scope_run(s, f, chn, conf_scope, &ts_steady, &ts_system, flt_otel_event_data[event].smp_opt_dir, err) == FLT_OTEL_RET_ERROR)
retval = FLT_OTEL_RET_ERROR;
}
#ifdef USE_OTEL_VARS
flt_otel_vars_dump(s);
#endif
flt_otel_http_headers_dump(chn);
OTELC_DBG(DEBUG, "event = %d %s, chn = %p, s->req = %p, s->res = %p", event, flt_otel_event_data[event].an_name, chn, &(s->req), &(s->res));
OTELC_RETURN_INT(retval);
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

File diff suppressed because it is too large Load diff

View file

@ -1,378 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
/* Group data table built from the X-macro list. */
#define FLT_OTEL_GROUP_DEF(a,b,c) { a, b, c },
const struct flt_otel_group_data flt_otel_group_data[] = { FLT_OTEL_GROUP_DEFINES };
#undef FLT_OTEL_GROUP_DEF
/***
* NAME
* flt_otel_group_action - group action execution callback
*
* SYNOPSIS
* static enum act_return flt_otel_group_action(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int opts)
*
* ARGUMENTS
* rule - action rule containing group configuration references
* px - proxy instance
* sess - current session
* s - current stream
* opts - action options (ACT_OPT_* flags)
*
* DESCRIPTION
* Executes the action_ptr callback for the FLT_OTEL_ACTION_GROUP action.
* Retrieves the filter configuration, group definition, and runtime context
* from the rule's argument pointers. If the filter is disabled or not
* attached to the stream, processing is skipped. Otherwise, iterates over
* all scopes defined in the group and runs each via flt_otel_scope_run().
* Scope execution errors are logged but do not prevent the remaining scopes
* from executing.
*
* RETURN VALUE
* Returns ACT_RET_CONT.
*/
static enum act_return flt_otel_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_otel_conf *conf;
const struct flt_otel_conf_group *conf_group;
const struct flt_otel_runtime_context *rt_ctx = NULL;
const struct flt_otel_conf_ph *ph_scope;
char *err = NULL;
int i, rc;
OTELC_FUNC("%p, %p, %p, %p, %d", rule, px, sess, s, opts);
OTELC_DBG(DEBUG, "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_OTEL_ARG_FLT_CONF];
conf = rule->arg.act.p[FLT_OTEL_ARG_CONF];
conf_group = ((const struct flt_otel_conf_ph *)(rule->arg.act.p[FLT_OTEL_ARG_GROUP]))->ptr;
if ((fconf == NULL) || (conf == NULL) || (conf_group == NULL)) {
FLT_OTEL_LOG(LOG_ERR, FLT_OTEL_ACTION_GROUP ": internal error, invalid group action");
OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
if (_HA_ATOMIC_LOAD(&(conf->instr->flag_disabled))) {
OTELC_DBG(INFO, "filter '%s' disabled, group action '%s' ignored", conf->id, conf_group->id);
OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
/* Find the OpenTelemetry 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) {
OTELC_DBG(INFO, "cannot find filter, probably not attached to the stream");
OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
else if (flt_otel_is_disabled(filter FLT_OTEL_DBG_ARGS(, -1))) {
OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
else {
OTELC_DBG(DEBUG, "run group '%s'", conf_group->id);
FLT_OTEL_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 < OTELC_TABLESIZE(flt_otel_group_data); i++)
if (flt_otel_group_data[i].act_from == rule->from)
break;
if (i >= OTELC_TABLESIZE(flt_otel_group_data)) {
FLT_OTEL_LOG(LOG_ERR, FLT_OTEL_ACTION_GROUP ": internal error, invalid rule->from=%d", rule->from);
OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
/* Execute each scope defined in this group. */
list_for_each_entry(ph_scope, &(conf_group->ph_scopes), list) {
rc = flt_otel_scope_run(s, rt_ctx->filter, (flt_otel_group_data[i].smp_opt_dir == SMP_OPT_DIR_REQ) ? &(s->req) : &(s->res), ph_scope->ptr, NULL, NULL, flt_otel_group_data[i].smp_opt_dir, &err);
if ((rc == FLT_OTEL_RET_ERROR) && (opts & ACT_OPT_FINAL)) {
FLT_OTEL_LOG(LOG_ERR, FLT_OTEL_ACTION_GROUP ": scope '%s' failed in group '%s'", ph_scope->id, conf_group->id);
OTELC_SFREE_CLEAR(err);
}
}
OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}
/***
* NAME
* flt_otel_group_check - group action post-parse check callback
*
* SYNOPSIS
* static int flt_otel_group_check(struct act_rule *rule, struct proxy *px, char **err)
*
* ARGUMENTS
* rule - action rule to validate
* px - proxy instance
* err - indirect pointer to error message string
*
* DESCRIPTION
* Validates the check_ptr callback for the FLT_OTEL_ACTION_GROUP action.
* Resolves the filter ID and group ID string references stored during parsing
* into direct pointers to the filter configuration and group configuration
* structures. Searches the proxy's filter list for a matching OTel filter,
* then locates the named group within that filter's configuration. On
* success, replaces the string ID pointers in <rule>->arg.act.p with the
* resolved configuration pointers.
*
* RETURN VALUE
* Returns 1 on success, or 0 on failure with <err> filled.
*/
static int flt_otel_group_check(struct act_rule *rule, struct proxy *px, char **err)
{
struct flt_conf *fconf_tmp, *fconf = NULL;
struct flt_otel_conf *conf;
struct flt_otel_conf_ph *ph_group;
const char *filter_id;
const char *group_id;
bool flag_found = 0;
int i;
OTELC_FUNC("%p, %p, %p:%p", rule, px, OTELC_DPTR_ARGS(err));
filter_id = rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID];
group_id = rule->arg.act.p[FLT_OTEL_ARG_GROUP_ID];
OTELC_DBG(NOTICE, "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 < OTELC_TABLESIZE(flt_otel_group_data); i++)
if (flt_otel_group_data[i].act_from == rule->from)
break;
if (i >= OTELC_TABLESIZE(flt_otel_group_data)) {
FLT_OTEL_ERR("internal error, unexpected rule->from=%d, please report this bug!", rule->from);
OTELC_RETURN_INT(0);
}
/*
* Try to find the OpenTelemetry 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 != otel_flt_id) {
/* This is not an OpenTelemetry filter. */
continue;
}
else if (strcmp(conf->id, filter_id) == 0) {
/* This is the good filter ID. */
fconf = fconf_tmp;
break;
}
}
if (fconf == NULL) {
FLT_OTEL_ERR("unable to find the OpenTelemetry filter '%s' used by the " FLT_OTEL_ACTION_GROUP " '%s'", filter_id, group_id);
OTELC_RETURN_INT(0);
}
/*
* Attempt to find if the group is defined in the OpenTelemetry filter
* configuration.
*/
list_for_each_entry(ph_group, &(conf->instr->ph_groups), list)
if (strcmp(ph_group->id, group_id) == 0) {
flag_found = 1;
break;
}
if (!flag_found) {
FLT_OTEL_ERR("unable to find group '%s' in the OpenTelemetry filter '%s' configuration", group_id, filter_id);
OTELC_RETURN_INT(0);
}
OTELC_SFREE_CLEAR(rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID]);
OTELC_SFREE_CLEAR(rule->arg.act.p[FLT_OTEL_ARG_GROUP_ID]);
/* Replace string IDs with resolved configuration pointers. */
rule->arg.act.p[FLT_OTEL_ARG_FLT_CONF] = fconf;
rule->arg.act.p[FLT_OTEL_ARG_CONF] = conf;
rule->arg.act.p[FLT_OTEL_ARG_GROUP] = ph_group;
OTELC_RETURN_INT(1);
}
/***
* NAME
* flt_otel_group_release - group action release callback
*
* SYNOPSIS
* static void flt_otel_group_release(struct act_rule *rule)
*
* ARGUMENTS
* rule - action rule being released
*
* DESCRIPTION
* Provides the release_ptr callback for the FLT_OTEL_ACTION_GROUP action.
* This is a no-op because the group action's argument pointers reference
* shared configuration structures that are freed separately during filter
* deinitialization.
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_otel_group_release(struct act_rule *rule)
{
OTELC_FUNC("%p", rule);
OTELC_RETURN();
}
/***
* NAME
* flt_otel_group_parse - group action keyword parser
*
* SYNOPSIS
* static enum act_parse_ret flt_otel_group_parse(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err)
*
* ARGUMENTS
* args - configuration line arguments array
* cur_arg - pointer to the current argument index
* px - proxy instance
* rule - action rule to populate
* err - indirect pointer to error message string
*
* DESCRIPTION
* Parses the FLT_OTEL_ACTION_GROUP action keyword from HAProxy configuration
* rules. Expects two arguments: a filter ID and a group ID, optionally
* followed by "if" or "unless" conditions. The filter ID and group ID are
* duplicated and stored in the <rule>'s argument pointers for later
* resolution by flt_otel_group_check(). The <rule>'s callbacks are set to
* flt_otel_group_action(), flt_otel_group_check(), and
* flt_otel_group_release(). This parser is registered for tcp-request,
* tcp-response, http-request, http-response, and http-after-response action
* contexts.
*
* RETURN VALUE
* Returns ACT_RET_PRS_OK on success, or ACT_RET_PRS_ERR on failure.
*/
static enum act_parse_ret flt_otel_group_parse(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err)
{
OTELC_FUNC("%p, %p, %p, %p, %p:%p", args, cur_arg, px, rule, OTELC_DPTR_ARGS(err));
FLT_OTEL_ARGS_DUMP();
if (!FLT_OTEL_ARG_ISVALID(*cur_arg) || !FLT_OTEL_ARG_ISVALID(*cur_arg + 1) ||
(FLT_OTEL_ARG_ISVALID(*cur_arg + 2) &&
!FLT_OTEL_PARSE_KEYWORD(*cur_arg + 2, FLT_OTEL_CONDITION_IF) &&
!FLT_OTEL_PARSE_KEYWORD(*cur_arg + 2, FLT_OTEL_CONDITION_UNLESS))) {
FLT_OTEL_ERR("expects: <filter-id> <group-id> [{ if | unless } ...]");
OTELC_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
}
/* Copy the OpenTelemetry filter id. */
rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID] = OTELC_STRDUP(args[*cur_arg]);
if (rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID] == NULL) {
FLT_OTEL_ERR("%s : out of memory", args[*cur_arg]);
OTELC_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
}
/* Copy the OpenTelemetry group id. */
rule->arg.act.p[FLT_OTEL_ARG_GROUP_ID] = OTELC_STRDUP(args[*cur_arg + 1]);
if (rule->arg.act.p[FLT_OTEL_ARG_GROUP_ID] == NULL) {
FLT_OTEL_ERR("%s : out of memory", args[*cur_arg + 1]);
OTELC_SFREE_CLEAR(rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID]);
OTELC_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
}
/* Wire up the rule callbacks. */
rule->action = ACT_CUSTOM;
rule->action_ptr = flt_otel_group_action;
rule->check_ptr = flt_otel_group_check;
rule->release_ptr = flt_otel_group_release;
*cur_arg += 2;
OTELC_RETURN_EX(ACT_RET_PRS_OK, enum act_parse_ret, "%d");
}
/* TCP request content action keywords for the OTel group action. */
static struct action_kw_list tcp_req_action_kws = { ILH, {
{ FLT_OTEL_ACTION_GROUP, flt_otel_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_action_kws);
/* TCP response content action keywords for the OTel group action. */
static struct action_kw_list tcp_res_action_kws = { ILH, {
{ FLT_OTEL_ACTION_GROUP, flt_otel_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_action_kws);
/* HTTP request action keywords for the OTel group action. */
static struct action_kw_list http_req_action_kws = { ILH, {
{ FLT_OTEL_ACTION_GROUP, flt_otel_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_action_kws);
/* HTTP response action keywords for the OTel group action. */
static struct action_kw_list http_res_action_kws = { ILH, {
{ FLT_OTEL_ACTION_GROUP, flt_otel_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_action_kws);
/* HTTP after-response action keywords for the OTel group action. */
static struct action_kw_list http_after_res_actions_kws = { ILH, {
{ FLT_OTEL_ACTION_GROUP, flt_otel_group_parse },
{ /* END */ },
}
};
INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions_kws);
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,324 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
#ifdef DEBUG_OTEL
/***
* NAME
* flt_otel_http_headers_dump - debug HTTP headers dump
*
* SYNOPSIS
* void flt_otel_http_headers_dump(const struct channel *chn)
*
* ARGUMENTS
* chn - channel to dump HTTP headers from
*
* DESCRIPTION
* Dumps all HTTP headers from the channel's HTX buffer. Iterates over HTX
* blocks, logging each header name-value pair at NOTICE level. Processing
* stops at the end-of-headers marker.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_http_headers_dump(const struct channel *chn)
{
const struct htx *htx;
int32_t pos;
OTELC_FUNC("%p", chn);
if (chn == NULL)
OTELC_RETURN();
htx = htxbuf(&(chn->buf));
if (htx_is_empty(htx))
OTELC_RETURN();
/* Walk HTX blocks and log each header until end-of-headers. */
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);
OTELC_DBG(NOTICE, "'%.*s: %.*s'", (int)n.len, n.ptr, (int)v.len, v.ptr);
}
else if (type == HTX_BLK_EOH)
break;
}
OTELC_RETURN();
}
#endif /* DEBUG_OTEL */
/***
* NAME
* flt_otel_http_headers_get - HTTP header extraction to text map
*
* SYNOPSIS
* struct otelc_text_map *flt_otel_http_headers_get(struct channel *chn, const char *prefix, size_t len, char **err)
*
* ARGUMENTS
* chn - channel containing HTTP headers
* prefix - header name prefix to match (or NULL for all)
* len - length of the prefix string
* err - indirect pointer to error message string
*
* DESCRIPTION
* Extracts HTTP headers matching a <prefix> from the channel's HTX buffer
* into a newly allocated text map. When <prefix> is NULL or its length is
* zero, all headers are extracted. If the prefix starts with
* FLT_OTEL_PARSE_CTX_IGNORE_NAME, prefix matching is bypassed. The prefix
* (including the separator dash) is stripped from header names before storing
* in the text map. Empty header values are replaced with an empty string to
* avoid misinterpretation by otelc_text_map_add(). This function is used by
* the "extract" keyword to read span context from incoming request headers.
*
* RETURN VALUE
* Returns a pointer to the populated text map, or NULL on failure or when
* no matching headers are found.
*/
struct otelc_text_map *flt_otel_http_headers_get(struct channel *chn, const char *prefix, size_t len, char **err)
{
const struct htx *htx;
size_t prefix_len = (!OTELC_STR_IS_VALID(prefix) || (len == 0)) ? 0 : (len + 1);
int32_t pos;
struct otelc_text_map *retptr = NULL;
OTELC_FUNC("%p, \"%s\", %zu, %p:%p", chn, OTELC_STR_ARG(prefix), len, OTELC_DPTR_ARGS(err));
if (chn == NULL)
OTELC_RETURN_PTR(retptr);
/*
* The keyword 'inject' allows you to define the name of the OpenTelemetry
* context without using a prefix. In that case all HTTP headers are
* transferred because it is not possible to separate them from the
* OpenTelemetry 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_OTEL_PARSE_CTX_IGNORE_NAME.
*/
if (OTELC_STR_IS_VALID(prefix) && (*prefix == FLT_OTEL_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 = OTELC_TEXT_MAP_NEW(NULL, 8);
if (retptr == NULL) {
FLT_OTEL_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
* otelc_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 (OTELC_TEXT_MAP_ADD(retptr, n.ptr + prefix_len, n.len - prefix_len, v.ptr, v.len, OTELC_TEXT_MAP_AUTO) == -1) {
FLT_OTEL_ERR("failed to add HTTP header data");
otelc_text_map_destroy(&retptr);
break;
}
}
}
else if (type == HTX_BLK_EOH)
break;
}
OTELC_TEXT_MAP_DUMP(retptr, "extracted HTTP headers");
if ((retptr != NULL) && (retptr->count == 0)) {
OTELC_DBG(NOTICE, "WARNING: no HTTP headers found");
otelc_text_map_destroy(&retptr);
}
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_http_header_set - HTTP header set or remove
*
* SYNOPSIS
* int flt_otel_http_header_set(struct channel *chn, const char *prefix, const char *name, const char *value, char **err)
*
* ARGUMENTS
* chn - channel containing HTTP headers
* prefix - header name prefix (or NULL)
* name - header name suffix (or NULL)
* value - header value to set (or NULL to remove only)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Sets or removes an HTTP header in the channel's HTX buffer. The full
* header name is constructed by combining <prefix> and <name> with a dash
* separator; if only one is provided, it is used directly. All existing
* occurrences of the header are removed first. If <name> is NULL, all
* headers starting with <prefix> are removed. If <value> is non-NULL, the
* header is then added with the new value. A NULL <value> causes only the
* removal, with no subsequent addition.
*
* RETURN VALUE
* Returns 0 on success, or FLT_OTEL_RET_ERROR on failure.
*/
int flt_otel_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 = FLT_OTEL_RET_ERROR;
OTELC_FUNC("%p, \"%s\", \"%s\", \"%s\", %p:%p", chn, OTELC_STR_ARG(prefix), OTELC_STR_ARG(name), OTELC_STR_ARG(value), OTELC_DPTR_ARGS(err));
if ((chn == NULL) || (!OTELC_STR_IS_VALID(prefix) && !OTELC_STR_IS_VALID(name)))
OTELC_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_OTEL_ERR("HTX is empty");
OTELC_RETURN_INT(retval);
}
if (!OTELC_STR_IS_VALID(prefix)) {
ist_name = ist2((char *)name, strlen(name));
}
else if (!OTELC_STR_IS_VALID(name)) {
ist_name = ist2((char *)prefix, strlen(prefix));
}
else {
buffer = flt_otel_trash_alloc(0, err);
if (buffer == NULL)
OTELC_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_OTEL
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 (!OTELC_STR_IS_VALID(name))
n.len = ist_name.len;
if (isteqi(n, ist_name))
if (http_remove_header(htx, &ctx) == 1)
OTELC_DBG(DEBUG, "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) {
retval = 0;
}
else if (http_add_header(htx, ist_name, ist(value)) == 1) {
retval = 0;
OTELC_DBG(DEBUG, "HTTP header '%s: %s' added", ist_name.ptr, value);
}
else {
FLT_OTEL_ERR("failed to set HTTP header '%s: %s'", ist_name.ptr, value);
}
flt_otel_trash_free(&buffer);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_http_headers_remove - HTTP headers removal by prefix
*
* SYNOPSIS
* int flt_otel_http_headers_remove(struct channel *chn, const char *prefix, char **err)
*
* ARGUMENTS
* chn - channel containing HTTP headers
* prefix - header name prefix to match for removal
* err - indirect pointer to error message string
*
* DESCRIPTION
* Removes all HTTP headers matching the given <prefix> from the channel's HTX
* buffer. This is a convenience wrapper around flt_otel_http_header_set()
* with NULL <name> and <value> arguments.
*
* RETURN VALUE
* Returns 0 on success, or FLT_OTEL_RET_ERROR on failure.
*/
int flt_otel_http_headers_remove(struct channel *chn, const char *prefix, char **err)
{
int retval;
OTELC_FUNC("%p, \"%s\", %p:%p", chn, OTELC_STR_ARG(prefix), OTELC_DPTR_ARGS(err));
retval = flt_otel_http_header_set(chn, prefix, NULL, NULL, err);
OTELC_RETURN_INT(retval);
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,289 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
/***
* NAME
* flt_otel_text_map_writer_set_cb - text map injection writer callback
*
* SYNOPSIS
* static int flt_otel_text_map_writer_set_cb(struct otelc_text_map_writer *writer, const char *key, const char *value)
*
* ARGUMENTS
* writer - text map writer instance
* key - context key name
* value - context key value
*
* DESCRIPTION
* Writer callback for text map injection. Called by the OTel C wrapper
* library during span context injection to store each key-value pair in the
* <writer>'s text map.
*
* RETURN VALUE
* Returns the result of OTELC_TEXT_MAP_ADD().
*/
static int flt_otel_text_map_writer_set_cb(struct otelc_text_map_writer *writer, const char *key, const char *value)
{
OTELC_FUNC("%p, \"%s\", \"%s\"", writer, OTELC_STR_ARG(key), OTELC_STR_ARG(value));
OTELC_RETURN_INT(OTELC_TEXT_MAP_ADD(&(writer->text_map), key, 0, value, 0, OTELC_TEXT_MAP_AUTO));
}
/***
* NAME
* flt_otel_http_headers_writer_set_cb - HTTP headers injection writer callback
*
* SYNOPSIS
* static int flt_otel_http_headers_writer_set_cb(struct otelc_http_headers_writer *writer, const char *key, const char *value)
*
* ARGUMENTS
* writer - HTTP headers writer instance
* key - context key name
* value - context key value
*
* DESCRIPTION
* Writer callback for HTTP headers injection. Called by the OTel C wrapper
* library during span context injection to store each key-value pair in the
* <writer>'s text map.
*
* RETURN VALUE
* Returns the result of OTELC_TEXT_MAP_ADD().
*/
static int flt_otel_http_headers_writer_set_cb(struct otelc_http_headers_writer *writer, const char *key, const char *value)
{
OTELC_FUNC("%p, \"%s\", \"%s\"", writer, OTELC_STR_ARG(key), OTELC_STR_ARG(value));
OTELC_RETURN_INT(OTELC_TEXT_MAP_ADD(&(writer->text_map), key, 0, value, 0, OTELC_TEXT_MAP_AUTO));
}
/***
* NAME
* flt_otel_inject_text_map - text map context injection
*
* SYNOPSIS
* int flt_otel_inject_text_map(const struct otelc_span *span, struct otelc_text_map_writer *carrier)
*
* ARGUMENTS
* span - span instance to inject context from
* carrier - text map writer carrier
*
* DESCRIPTION
* Injects the span context into a text map carrier. Initializes the
* <carrier> structure, sets the writer callback to
* flt_otel_text_map_writer_set_cb(), and delegates to the <span>'s
* inject_text_map() method.
*
* RETURN VALUE
* Returns the result of the <span>'s inject_text_map() method,
* or FLT_OTEL_RET_ERROR if arguments are NULL.
*/
int flt_otel_inject_text_map(const struct otelc_span *span, struct otelc_text_map_writer *carrier)
{
OTELC_FUNC("%p, %p", span, carrier);
if ((span == NULL) || (carrier == NULL))
OTELC_RETURN_INT(FLT_OTEL_RET_ERROR);
(void)memset(carrier, 0, sizeof(*carrier));
carrier->set = flt_otel_text_map_writer_set_cb;
OTELC_RETURN_INT(OTELC_OPS(span, inject_text_map, carrier));
}
/***
* NAME
* flt_otel_inject_http_headers - HTTP headers context injection
*
* SYNOPSIS
* int flt_otel_inject_http_headers(const struct otelc_span *span, struct otelc_http_headers_writer *carrier)
*
* ARGUMENTS
* span - span instance to inject context from
* carrier - HTTP headers writer carrier
*
* DESCRIPTION
* Injects the span context into an HTTP headers carrier. Initializes the
* <carrier> structure, sets the writer callback to
* flt_otel_http_headers_writer_set_cb(), and delegates to the <span>'s
* inject_http_headers() method.
*
* RETURN VALUE
* Returns the result of the <span>'s inject_http_headers() method,
* or FLT_OTEL_RET_ERROR if arguments are NULL.
*/
int flt_otel_inject_http_headers(const struct otelc_span *span, struct otelc_http_headers_writer *carrier)
{
OTELC_FUNC("%p, %p", span, carrier);
if ((span == NULL) || (carrier == NULL))
OTELC_RETURN_INT(FLT_OTEL_RET_ERROR);
(void)memset(carrier, 0, sizeof(*carrier));
carrier->set = flt_otel_http_headers_writer_set_cb;
OTELC_RETURN_INT(OTELC_OPS(span, inject_http_headers, carrier));
}
/***
* NAME
* flt_otel_text_map_reader_foreach_key_cb - text map extraction reader callback
*
* SYNOPSIS
* static int flt_otel_text_map_reader_foreach_key_cb(const struct otelc_text_map_reader *reader, int (*handler)(void *arg, const char *key, const char *value), void *arg)
*
* ARGUMENTS
* reader - text map reader instance
* handler - callback function invoked for each key-value pair
* arg - opaque argument passed to the handler
*
* DESCRIPTION
* Reader callback for text map extraction. Iterates over all key-value
* pairs in the <reader>'s text map and invokes <handler> for each. Iteration
* stops if the <handler> returns -1.
*
* RETURN VALUE
* Returns the last <handler> return value, or 0 if the text map is empty.
*/
static int flt_otel_text_map_reader_foreach_key_cb(const struct otelc_text_map_reader *reader, int (*handler)(void *arg, const char *key, const char *value), void *arg)
{
size_t i;
int retval = 0;
OTELC_FUNC("%p, %p, %p", reader, handler, arg);
for (i = 0; (retval != -1) && (i < reader->text_map.count); i++) {
OTELC_DBG(OTELC, "\"%s\" -> \"%s\"", OTELC_STR_ARG(reader->text_map.key[i]), OTELC_STR_ARG(reader->text_map.value[i]));
retval = handler(arg, reader->text_map.key[i], reader->text_map.value[i]);
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_http_headers_reader_foreach_key_cb - HTTP headers extraction reader callback
*
* SYNOPSIS
* static int flt_otel_http_headers_reader_foreach_key_cb(const struct otelc_http_headers_reader *reader, int (*handler)(void *arg, const char *key, const char *value), void *arg)
*
* ARGUMENTS
* reader - HTTP headers reader instance
* handler - callback function invoked for each key-value pair
* arg - opaque argument passed to the handler
*
* DESCRIPTION
* Reader callback for HTTP headers extraction. Iterates over all key-value
* pairs in the <reader>'s text map and invokes <handler> for each. Iteration
* stops if the <handler> returns -1.
*
* RETURN VALUE
* Returns the last <handler> return value, or 0 if the text map is empty.
*/
static int flt_otel_http_headers_reader_foreach_key_cb(const struct otelc_http_headers_reader *reader, int (*handler)(void *arg, const char *key, const char *value), void *arg)
{
size_t i;
int retval = 0;
OTELC_FUNC("%p, %p, %p", reader, handler, arg);
for (i = 0; (retval != -1) && (i < reader->text_map.count); i++) {
OTELC_DBG(OTELC, "\"%s\" -> \"%s\"", OTELC_STR_ARG(reader->text_map.key[i]), OTELC_STR_ARG(reader->text_map.value[i]));
retval = handler(arg, reader->text_map.key[i], reader->text_map.value[i]);
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_extract_text_map - text map context extraction
*
* SYNOPSIS
* struct otelc_span_context *flt_otel_extract_text_map(struct otelc_tracer *tracer, struct otelc_text_map_reader *carrier, const struct otelc_text_map *text_map)
*
* ARGUMENTS
* tracer - OTel tracer instance
* carrier - text map reader carrier
* text_map - text map containing the context data (or NULL)
*
* DESCRIPTION
* Extracts a span context from a text map carrier via the <tracer>.
* Initializes the <carrier> structure, sets the foreach_key callback to
* flt_otel_text_map_reader_foreach_key_cb(), and copies the <text_map> data
* into the <carrier>. Delegates to the <tracer>'s extract_text_map() method.
*
* RETURN VALUE
* Returns a pointer to the extracted span context, or NULL on failure.
*/
struct otelc_span_context *flt_otel_extract_text_map(struct otelc_tracer *tracer, struct otelc_text_map_reader *carrier, const struct otelc_text_map *text_map)
{
OTELC_FUNC("%p, %p, %p", tracer, carrier, text_map);
if ((tracer == NULL) || (carrier == NULL))
OTELC_RETURN_PTR(NULL);
(void)memset(carrier, 0, sizeof(*carrier));
carrier->foreach_key = flt_otel_text_map_reader_foreach_key_cb;
if (text_map != NULL)
(void)memcpy(&(carrier->text_map), text_map, sizeof(carrier->text_map));
OTELC_RETURN_PTR(OTELC_OPS(tracer, extract_text_map, carrier));
}
/***
* NAME
* flt_otel_extract_http_headers - HTTP headers context extraction
*
* SYNOPSIS
* struct otelc_span_context *flt_otel_extract_http_headers(struct otelc_tracer *tracer, struct otelc_http_headers_reader *carrier, const struct otelc_text_map *text_map)
*
* ARGUMENTS
* tracer - OTel tracer instance
* carrier - HTTP headers reader carrier
* text_map - text map containing the context data (or NULL)
*
* DESCRIPTION
* Extracts a span context from an HTTP headers carrier via the <tracer>.
* Initializes the <carrier> structure, sets the foreach_key callback to
* flt_otel_http_headers_reader_foreach_key_cb(), and copies the <text_map>
* data into the <carrier>. Delegates to the <tracer>'s
* extract_http_headers() method.
*
* RETURN VALUE
* Returns a pointer to the extracted span context, or NULL on failure.
*/
struct otelc_span_context *flt_otel_extract_http_headers(struct otelc_tracer *tracer, struct otelc_http_headers_reader *carrier, const struct otelc_text_map *text_map)
{
OTELC_FUNC("%p, %p, %p", tracer, carrier, text_map);
if ((tracer == NULL) || (carrier == NULL))
OTELC_RETURN_PTR(NULL);
(void)memset(carrier, 0, sizeof(*carrier));
carrier->foreach_key = flt_otel_http_headers_reader_foreach_key_cb;
if (text_map != NULL)
(void)memcpy(&(carrier->text_map), text_map, sizeof(carrier->text_map));
OTELC_RETURN_PTR(OTELC_OPS(tracer, extract_http_headers, carrier));
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

File diff suppressed because it is too large Load diff

View file

@ -1,385 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
struct pool_head *pool_head_otel_scope_span __read_mostly = NULL;
struct pool_head *pool_head_otel_scope_context __read_mostly = NULL;
struct pool_head *pool_head_otel_runtime_context __read_mostly = NULL;
struct pool_head *pool_head_otel_span_context __read_mostly = NULL;
#ifdef USE_POOL_OTEL_SCOPE_SPAN
REGISTER_POOL(&pool_head_otel_scope_span, "otel_scope_span", sizeof(struct flt_otel_scope_span));
#endif
#ifdef USE_POOL_OTEL_SCOPE_CONTEXT
REGISTER_POOL(&pool_head_otel_scope_context, "otel_scope_context", sizeof(struct flt_otel_scope_context));
#endif
#ifdef USE_POOL_OTEL_RUNTIME_CONTEXT
REGISTER_POOL(&pool_head_otel_runtime_context, "otel_runtime_context", sizeof(struct flt_otel_runtime_context));
#endif
#ifdef USE_POOL_OTEL_SPAN_CONTEXT
REGISTER_POOL(&pool_head_otel_span_context, "otel_span_context", MAX(sizeof(struct otelc_span), sizeof(struct otelc_span_context)));
#endif
/***
* NAME
* flt_otel_pool_alloc - pool-aware memory allocation
*
* SYNOPSIS
* void *flt_otel_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err)
*
* ARGUMENTS
* pool - HAProxy memory pool to allocate from (or NULL for heap)
* size - number of bytes to allocate
* flag_clear - whether to zero-fill the allocated memory
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates <size> bytes of memory from the HAProxy memory <pool>. If <pool>
* is NULL, the allocation falls back to the heap via OTELC_MALLOC(). When
* <flag_clear> is set, the allocated memory is zero-filled. On allocation
* failure, an error message is stored via <err>.
*
* RETURN VALUE
* Returns a pointer to the allocated memory, or NULL on failure.
*/
void *flt_otel_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err)
{
void *retptr;
OTELC_FUNC("%p, %zu, %hhu, %p:%p", pool, size, flag_clear, OTELC_DPTR_ARGS(err));
if (pool != NULL) {
retptr = pool_alloc(pool);
if (retptr != NULL)
OTELC_DBG(NOTICE, "POOL_ALLOC: %s:%d(%p %zu)", __func__, __LINE__, retptr, FLT_OTEL_DEREF(pool, size, size));
} else {
retptr = OTELC_MALLOC(size);
}
if (retptr == NULL)
FLT_OTEL_ERR("out of memory");
else if (flag_clear)
(void)memset(retptr, 0, size);
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_pool_strndup - pool-aware string duplication
*
* SYNOPSIS
* void *flt_otel_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err)
*
* ARGUMENTS
* pool - HAProxy memory pool to allocate from (or NULL for heap)
* s - source string to duplicate
* size - maximum number of characters to copy
* err - indirect pointer to error message string
*
* DESCRIPTION
* Duplicates up to <size> characters from the string <s> using the HAProxy
* memory <pool>. If <pool> is NULL, the duplication falls back to
* OTELC_STRNDUP(). When using a pool, the copy is truncated to <pool>->size-1
* bytes and null-terminated.
*
* RETURN VALUE
* Returns a pointer to the duplicated string, or NULL on failure.
*/
void *flt_otel_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err)
{
void *retptr;
OTELC_FUNC("%p, \"%.*s\", %zu, %p:%p", pool, (int)size, s, size, OTELC_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 = OTELC_STRNDUP(s, size);
}
if (retptr != NULL)
OTELC_DBG(NOTICE, "POOL_STRNDUP: %s:%d(%p %zu)", __func__, __LINE__, retptr, FLT_OTEL_DEREF(pool, size, size));
else
FLT_OTEL_ERR("out of memory");
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_pool_free - pool-aware memory deallocation
*
* SYNOPSIS
* void flt_otel_pool_free(struct pool_head *pool, void **ptr)
*
* ARGUMENTS
* pool - HAProxy memory pool to return memory to (or NULL for heap)
* ptr - indirect pointer to the memory to free
*
* DESCRIPTION
* Returns memory referenced by <*ptr> to the HAProxy memory <pool>. If
* <pool> is NULL, the memory is freed via OTELC_SFREE(). The pointer <*ptr>
* is set to NULL after freeing. If <ptr> is NULL or <*ptr> is already NULL,
* the function returns immediately.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_pool_free(struct pool_head *pool, void **ptr)
{
OTELC_FUNC("%p, %p:%p", pool, OTELC_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
OTELC_RETURN();
OTELC_DBG(NOTICE, "POOL_FREE: %s:%d(%p %u)", __func__, __LINE__, *ptr, FLT_OTEL_DEREF(pool, size, 0));
if (pool != NULL)
pool_free(pool, *ptr);
else
OTELC_SFREE(*ptr);
*ptr = NULL;
OTELC_RETURN();
}
/***
* NAME
* flt_otel_pool_init - OTel filter memory pool initialization
*
* SYNOPSIS
* int flt_otel_pool_init(void)
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* Initializes all memory pools used by the OTel filter. Each pool is
* created only when the corresponding USE_POOL_OTEL_* macro is defined.
*
* RETURN VALUE
* Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
*/
int flt_otel_pool_init(void)
{
int retval = FLT_OTEL_RET_OK;
OTELC_FUNC("");
#ifdef USE_POOL_OTEL_SCOPE_SPAN
FLT_OTEL_POOL_INIT(pool_head_otel_scope_span, "otel_scope_span", sizeof(struct flt_otel_scope_span), retval);
#endif
#ifdef USE_POOL_OTEL_SCOPE_CONTEXT
FLT_OTEL_POOL_INIT(pool_head_otel_scope_context, "otel_scope_context", sizeof(struct flt_otel_scope_context), retval);
#endif
#ifdef USE_POOL_OTEL_RUNTIME_CONTEXT
FLT_OTEL_POOL_INIT(pool_head_otel_runtime_context, "otel_runtime_context", sizeof(struct flt_otel_runtime_context), retval);
#endif
#ifdef USE_POOL_OTEL_SPAN_CONTEXT
FLT_OTEL_POOL_INIT(pool_head_otel_span_context, "otel_span_context", OTELC_MAX(sizeof(struct otelc_span), sizeof(struct otelc_span_context)), retval);
#endif
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_pool_destroy - OTel filter memory pool destruction
*
* SYNOPSIS
* void flt_otel_pool_destroy(void)
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* Destroys all memory pools used by the OTel filter. Each pool is
* destroyed only when the corresponding USE_POOL_OTEL_* macro is defined.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_pool_destroy(void)
{
OTELC_FUNC("");
#ifdef USE_POOL_OTEL_SCOPE_SPAN
FLT_OTEL_POOL_DESTROY(pool_head_otel_scope_span);
#endif
#ifdef USE_POOL_OTEL_SCOPE_CONTEXT
FLT_OTEL_POOL_DESTROY(pool_head_otel_scope_context);
#endif
#ifdef USE_POOL_OTEL_RUNTIME_CONTEXT
FLT_OTEL_POOL_DESTROY(pool_head_otel_runtime_context);
#endif
#ifdef USE_POOL_OTEL_SPAN_CONTEXT
FLT_OTEL_POOL_DESTROY(pool_head_otel_span_context);
#endif
OTELC_RETURN();
}
#ifdef DEBUG_OTEL
/***
* NAME
* flt_otel_pool_info - debug pool sizes logging
*
* SYNOPSIS
* void flt_otel_pool_info(void)
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* Logs the sizes of all registered HAProxy memory pools used by the OTel
* filter (buffer, trash, scope_span, scope_context, runtime_context).
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_pool_info(void)
{
OTELC_DBG(NOTICE, "--- pool info ----------");
/*
* In case we have some error in the configuration file,
* it is possible that this pool was not initialized.
*/
#ifdef USE_POOL_BUFFER
OTELC_DBG(NOTICE, " buffer: %p %u", pool_head_buffer, FLT_OTEL_DEREF(pool_head_buffer, size, 0));
#endif
#ifdef USE_TRASH_CHUNK
OTELC_DBG(NOTICE, " trash: %p %u", pool_head_trash, FLT_OTEL_DEREF(pool_head_trash, size, 0));
#endif
#ifdef USE_POOL_OTEL_SCOPE_SPAN
OTELC_DBG(NOTICE, " otel_scope_span: %p %u", pool_head_otel_scope_span, FLT_OTEL_DEREF(pool_head_otel_scope_span, size, 0));
#endif
#ifdef USE_POOL_OTEL_SCOPE_CONTEXT
OTELC_DBG(NOTICE, " otel_scope_context: %p %u", pool_head_otel_scope_context, FLT_OTEL_DEREF(pool_head_otel_scope_context, size, 0));
#endif
#ifdef USE_POOL_OTEL_RUNTIME_CONTEXT
OTELC_DBG(NOTICE, " otel_runtime_context: %p %u", pool_head_otel_runtime_context, FLT_OTEL_DEREF(pool_head_otel_runtime_context, size, 0));
#endif
#ifdef USE_POOL_OTEL_SPAN_CONTEXT
OTELC_DBG(NOTICE, " otel_span_context: %p %u", pool_head_otel_span_context, FLT_OTEL_DEREF(pool_head_otel_span_context, size, 0));
#endif
}
#endif /* DEBUG_OTEL */
/***
* NAME
* flt_otel_trash_alloc - trash buffer allocation
*
* SYNOPSIS
* struct buffer *flt_otel_trash_alloc(bool flag_clear, char **err)
*
* ARGUMENTS
* flag_clear - whether to zero-fill the buffer area
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates a temporary buffer chunk for use as scratch space. When
* USE_TRASH_CHUNK is defined, the buffer is obtained via alloc_trash_chunk();
* otherwise, a buffer structure and its data area are allocated from the heap
* using global.tune.bufsize as the buffer size. When <flag_clear> is set,
* the buffer's data area is zero-filled.
*
* RETURN VALUE
* Returns a pointer to the allocated buffer, or NULL on failure.
*/
struct buffer *flt_otel_trash_alloc(bool flag_clear, char **err)
{
struct buffer *retptr;
OTELC_FUNC("%hhu, %p:%p", flag_clear, OTELC_DPTR_ARGS(err));
#ifdef USE_TRASH_CHUNK
retptr = alloc_trash_chunk();
if (retptr != NULL)
OTELC_DBG(NOTICE, "TRASH_ALLOC: %s:%d(%p %zu)", __func__, __LINE__, retptr, retptr->size);
#else
retptr = OTELC_MALLOC(sizeof(*retptr));
if (retptr != NULL) {
chunk_init(retptr, OTELC_MALLOC(global.tune.bufsize), global.tune.bufsize);
if (retptr->area == NULL)
OTELC_SFREE_CLEAR(retptr);
else
*(retptr->area) = '\0';
}
#endif
if (retptr == NULL)
FLT_OTEL_ERR("out of memory");
else if (flag_clear)
(void)memset(retptr->area, 0, retptr->size);
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_trash_free - trash buffer deallocation
*
* SYNOPSIS
* void flt_otel_trash_free(struct buffer **ptr)
*
* ARGUMENTS
* ptr - indirect pointer to the buffer to free
*
* DESCRIPTION
* Frees a trash buffer chunk previously allocated by flt_otel_trash_alloc().
* When USE_TRASH_CHUNK is defined, the buffer is freed via
* free_trash_chunk(); otherwise, both the data area and the buffer structure
* are freed individually. The pointer <*ptr> is set to NULL after freeing.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_trash_free(struct buffer **ptr)
{
OTELC_FUNC("%p:%p", OTELC_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
OTELC_RETURN();
OTELC_DBG(NOTICE, "TRASH_FREE: %s:%d(%p %zu)", __func__, __LINE__, *ptr, (*ptr)->size);
#ifdef USE_TRASH_CHUNK
free_trash_chunk(*ptr);
#else
OTELC_SFREE((*ptr)->area);
OTELC_SFREE(*ptr);
#endif
*ptr = NULL;
OTELC_RETURN();
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -1,745 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
/***
* NAME
* flt_otel_runtime_context_init - per-stream runtime context allocation
*
* SYNOPSIS
* struct flt_otel_runtime_context *flt_otel_runtime_context_init(struct stream *s, struct filter *f, char **err)
*
* ARGUMENTS
* s - the stream to which the context belongs
* f - the filter instance
* err - indirect pointer to error message string
*
* DESCRIPTION
* Allocates and initializes a per-stream runtime context from pool memory.
* It copies the hard-error, disabled and logging flags from the filter
* configuration, generates a UUID and stores it in the sess.otel.uuid
* HAProxy variable.
*
* RETURN VALUE
* Returns a pointer to the new runtime context, or NULL on failure.
*/
struct flt_otel_runtime_context *flt_otel_runtime_context_init(struct stream *s, struct filter *f, char **err)
{
const struct flt_otel_conf *conf = FLT_OTEL_CONF(f);
struct buffer uuid;
struct flt_otel_runtime_context *retptr = NULL;
OTELC_FUNC("%p, %p, %p:%p", s, f, OTELC_DPTR_ARGS(err));
retptr = flt_otel_pool_alloc(pool_head_otel_runtime_context, sizeof(*retptr), 1, err);
if (retptr == NULL)
OTELC_RETURN_PTR(retptr);
/* Initialize runtime context fields and generate a session UUID. */
retptr->stream = s;
retptr->filter = f;
retptr->flag_harderr = _HA_ATOMIC_LOAD(&(conf->instr->flag_harderr));
retptr->flag_disabled = _HA_ATOMIC_LOAD(&(conf->instr->flag_disabled));
retptr->logging = _HA_ATOMIC_LOAD(&(conf->instr->logging));
retptr->idle_timeout = 0;
retptr->idle_exp = TICK_ETERNITY;
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_OTEL_VARS
/*
* The HAProxy variable 'sess.otel.uuid' is registered here,
* after which its value is set to runtime context UUID.
*/
if (flt_otel_var_register(FLT_OTEL_VAR_UUID, err) != -1)
(void)flt_otel_var_set(s, FLT_OTEL_VAR_UUID, retptr->uuid, SMP_OPT_DIR_REQ, err);
#endif
FLT_OTEL_DBG_RUNTIME_CONTEXT("session context: ", retptr);
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_runtime_context_free - per-stream runtime context cleanup
*
* SYNOPSIS
* void flt_otel_runtime_context_free(struct filter *f)
*
* ARGUMENTS
* f - the filter instance whose runtime context is to be freed
*
* DESCRIPTION
* Frees the per-stream runtime context attached to <f>. It ends all active
* spans with the current monotonic timestamp, destroys all extracted
* contexts, and returns the pool memory.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_runtime_context_free(struct filter *f)
{
struct flt_otel_runtime_context *rt_ctx = f->ctx;
OTELC_FUNC("%p", f);
if (rt_ctx == NULL)
OTELC_RETURN();
FLT_OTEL_DBG_RUNTIME_CONTEXT("session context: ", rt_ctx);
/* End all active spans with a common timestamp. */
if (!LIST_ISEMPTY(&(rt_ctx->spans))) {
struct timespec ts_steady;
struct flt_otel_scope_span *span, *span_back;
/* All spans should be completed at the same time. */
(void)clock_gettime(CLOCK_MONOTONIC, &ts_steady);
list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list) {
OTELC_OPSR(span->span, end_with_options, &ts_steady, OTELC_SPAN_STATUS_IGNORE, NULL);
flt_otel_scope_span_free(&span);
}
}
/* Destroy all extracted span contexts. */
if (!LIST_ISEMPTY(&(rt_ctx->contexts))) {
struct flt_otel_scope_context *ctx, *ctx_back;
list_for_each_entry_safe(ctx, ctx_back, &(rt_ctx->contexts), list)
flt_otel_scope_context_free(&ctx);
}
flt_otel_pool_free(pool_head_otel_runtime_context, &(f->ctx));
OTELC_RETURN();
}
/***
* NAME
* flt_otel_scope_span_init - scope span lookup or creation
*
* SYNOPSIS
* struct flt_otel_scope_span *flt_otel_scope_span_init(struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len, const char *ref_id, size_t ref_id_len, uint dir, char **err)
*
* ARGUMENTS
* rt_ctx - the runtime context owning the span list
* id - the span operation name
* id_len - length of the <id> string
* ref_id - the parent span or context name, or NULL
* ref_id_len - length of the <ref_id> string
* dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Finds an existing scope span by <id> in the runtime context or creates a
* new one. If <ref_id> is set, it resolves the parent reference by searching
* the span list first, then the extracted context list.
*
* RETURN VALUE
* Returns the existing or new scope span, or NULL on failure.
*/
struct flt_otel_scope_span *flt_otel_scope_span_init(struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len, const char *ref_id, size_t ref_id_len, uint dir, char **err)
{
struct otelc_span *ref_span = NULL;
struct otelc_span_context *ref_ctx = NULL;
struct flt_otel_scope_span *span, *retptr = NULL;
struct flt_otel_scope_context *ctx;
OTELC_FUNC("%p, \"%s\", %zu, \"%s\", %zu, %u, %p:%p", rt_ctx, OTELC_STR_ARG(id), id_len, OTELC_STR_ARG(ref_id), ref_id_len, dir, OTELC_DPTR_ARGS(err));
if ((rt_ctx == NULL) || (id == NULL))
OTELC_RETURN_PTR(retptr);
/* Return the existing span if one matches this ID. */
list_for_each_entry(span, &(rt_ctx->spans), list)
if (FLT_OTEL_CONF_STR_CMP(span->id, id)) {
OTELC_DBG(NOTICE, "found span %p", span);
OTELC_RETURN_PTR(span);
}
/* Resolve the parent reference from spans or contexts. */
if (ref_id != NULL) {
list_for_each_entry(span, &(rt_ctx->spans), list)
if (FLT_OTEL_CONF_STR_CMP(span->id, ref_id)) {
ref_span = span->span;
break;
}
if (ref_span != NULL) {
OTELC_DBG(NOTICE, "found referenced span %p", span);
} else {
list_for_each_entry(ctx, &(rt_ctx->contexts), list)
if (FLT_OTEL_CONF_STR_CMP(ctx->id, ref_id)) {
ref_ctx = ctx->context;
break;
}
if (ref_ctx != NULL) {
OTELC_DBG(NOTICE, "found referenced context %p", ctx);
} else {
FLT_OTEL_ERR("cannot find referenced span/context '%s'", ref_id);
OTELC_RETURN_PTR(retptr);
}
}
}
retptr = flt_otel_pool_alloc(pool_head_otel_scope_span, sizeof(*retptr), 1, err);
if (retptr == NULL)
OTELC_RETURN_PTR(retptr);
/* Populate the new scope span and insert it into the list. */
retptr->id = id;
retptr->id_len = id_len;
retptr->smp_opt_dir = dir;
retptr->ref_span = ref_span;
retptr->ref_ctx = ref_ctx;
LIST_INSERT(&(rt_ctx->spans), &(retptr->list));
FLT_OTEL_DBG_SCOPE_SPAN("new span ", retptr);
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_scope_span_free - scope span cleanup
*
* SYNOPSIS
* void flt_otel_scope_span_free(struct flt_otel_scope_span **ptr)
*
* ARGUMENTS
* ptr - pointer to the scope span pointer to free
*
* DESCRIPTION
* Frees a scope span entry pointed to by <ptr> and removes it from its list.
* If the OTel span is still active (non-NULL), the function refuses to free
* it and returns immediately.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_scope_span_free(struct flt_otel_scope_span **ptr)
{
OTELC_FUNC("%p:%p", OTELC_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
OTELC_RETURN();
FLT_OTEL_DBG_SCOPE_SPAN("", *ptr);
/* If the span is still active, do nothing. */
if ((*ptr)->span != NULL) {
OTELC_DBG(NOTICE, "cannot finish active span");
OTELC_RETURN();
}
FLT_OTEL_LIST_DEL(&((*ptr)->list));
flt_otel_pool_free(pool_head_otel_scope_span, (void **)ptr);
OTELC_RETURN();
}
/***
* NAME
* flt_otel_scope_context_init - scope context extraction
*
* SYNOPSIS
* struct flt_otel_scope_context *flt_otel_scope_context_init(struct flt_otel_runtime_context *rt_ctx, struct otelc_tracer *tracer, const char *id, size_t id_len, const struct otelc_text_map *text_map, uint dir, char **err)
*
* ARGUMENTS
* rt_ctx - the runtime context owning the context list
* tracer - the OTel tracer used for context extraction
* id - the context name
* id_len - length of the <id> string
* text_map - the carrier text map to extract from
* dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Finds an existing scope context by <id> in the runtime context or creates
* a new one by extracting the span context from the <text_map> carrier via
* the <tracer>.
*
* RETURN VALUE
* Returns the existing or new scope context, or NULL on failure.
*/
struct flt_otel_scope_context *flt_otel_scope_context_init(struct flt_otel_runtime_context *rt_ctx, struct otelc_tracer *tracer, const char *id, size_t id_len, const struct otelc_text_map *text_map, uint dir, char **err)
{
struct otelc_http_headers_reader reader;
struct otelc_span_context *span_ctx;
struct flt_otel_scope_context *retptr = NULL;
OTELC_FUNC("%p, %p, \"%s\", %zu, %p, %u, %p:%p", rt_ctx, tracer, OTELC_STR_ARG(id), id_len, text_map, dir, OTELC_DPTR_ARGS(err));
if ((rt_ctx == NULL) || (tracer == NULL) || (id == NULL) || (text_map == NULL))
OTELC_RETURN_PTR(retptr);
/* Return the existing context if one matches this ID. */
list_for_each_entry(retptr, &(rt_ctx->contexts), list)
if (FLT_OTEL_CONF_STR_CMP(retptr->id, id)) {
OTELC_DBG(NOTICE, "found context %p", retptr);
OTELC_RETURN_PTR(retptr);
}
retptr = flt_otel_pool_alloc(pool_head_otel_scope_context, sizeof(*retptr), 1, err);
if (retptr == NULL)
OTELC_RETURN_PTR(retptr);
span_ctx = flt_otel_extract_http_headers(tracer, &reader, text_map);
if (span_ctx == NULL) {
flt_otel_scope_context_free(&retptr);
OTELC_RETURN_PTR(retptr);
}
/* Populate the new scope context and insert it into the list. */
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_OTEL_DBG_SCOPE_CONTEXT("new context ", retptr);
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_scope_context_free - scope context cleanup
*
* SYNOPSIS
* void flt_otel_scope_context_free(struct flt_otel_scope_context **ptr)
*
* ARGUMENTS
* ptr - pointer to the scope context pointer to free
*
* DESCRIPTION
* Frees a scope context entry pointed to by <ptr>. It destroys the
* underlying OTel span context, removes the entry from its list, and
* returns the pool memory.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_scope_context_free(struct flt_otel_scope_context **ptr)
{
OTELC_FUNC("%p:%p", OTELC_DPTR_ARGS(ptr));
if ((ptr == NULL) || (*ptr == NULL))
OTELC_RETURN();
FLT_OTEL_DBG_SCOPE_CONTEXT("", *ptr);
if ((*ptr)->context != NULL)
OTELC_OPSR((*ptr)->context, destroy);
FLT_OTEL_LIST_DEL(&((*ptr)->list));
flt_otel_pool_free(pool_head_otel_scope_context, (void **)ptr);
OTELC_RETURN();
}
#ifdef DEBUG_OTEL
/***
* NAME
* flt_otel_scope_data_dump - debug scope data dump
*
* SYNOPSIS
* void flt_otel_scope_data_dump(const struct flt_otel_scope_data *data)
*
* ARGUMENTS
* data - the scope data structure to dump
*
* DESCRIPTION
* Dumps the contents of a scope <data> structure for debugging: baggage
* key-value pairs, attributes, events with their attributes, span links,
* and the status code/description.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_scope_data_dump(const struct flt_otel_scope_data *data)
{
size_t i;
if (data == NULL)
return;
if (data->baggage.attr == NULL) {
OTELC_DBG(WORKER, "baggage %p:{ }", &(data->baggage));
} else {
OTELC_DBG(WORKER, "baggage %p:{", &(data->baggage));
for (i = 0; i < data->baggage.cnt; i++)
OTELC_DBG_KV(WORKER, " ", data->baggage.attr + i);
OTELC_DBG(WORKER, "}");
}
if (data->attributes.attr == NULL) {
OTELC_DBG(WORKER, "attributes %p:{ }", &(data->attributes));
} else {
OTELC_DBG(WORKER, "attributes %p:{", &(data->attributes));
for (i = 0; i < data->attributes.cnt; i++)
OTELC_DBG_KV(WORKER, " ", data->attributes.attr + i);
OTELC_DBG(WORKER, "}");
}
if (LIST_ISEMPTY(&(data->events))) {
OTELC_DBG(WORKER, "events %p:{ }", &(data->events));
} else {
struct flt_otel_scope_data_event *event;
OTELC_DBG(WORKER, "events %p:{", &(data->events));
list_for_each_entry_rev(event, &(data->events), list) {
OTELC_DBG(WORKER, " '%s' %zu/%zu", event->name, event->cnt, event->size);
if (event->attr != NULL)
for (i = 0; i < event->cnt; i++)
OTELC_DBG_KV(WORKER, " ", event->attr + i);
}
OTELC_DBG(WORKER, "}");
}
if (LIST_ISEMPTY(&(data->links))) {
OTELC_DBG(WORKER, "links %p:{ }", &(data->links));
} else {
struct flt_otel_scope_data_link *link;
OTELC_DBG(WORKER, "links %p:{", &(data->links));
list_for_each_entry(link, &(data->links), list)
OTELC_DBG(WORKER, " %p %p", link->span, link->context);
OTELC_DBG(WORKER, "}");
}
if ((data->status.code == 0) && (data->status.description == NULL))
OTELC_DBG(WORKER, "status %p:{ }", &(data->status));
else
FLT_OTEL_DBG_SCOPE_DATA_STATUS("status ", &(data->status));
}
#endif /* DEBUG_OTEL */
/***
* NAME
* flt_otel_scope_data_init - scope data zero-initialization
*
* SYNOPSIS
* void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr)
*
* ARGUMENTS
* ptr - the scope data structure to initialize
*
* DESCRIPTION
* Zero-initializes the scope data structure pointed to by <ptr> and sets up
* the event and link list heads.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr)
{
OTELC_FUNC("%p", ptr);
if (ptr == NULL)
OTELC_RETURN();
(void)memset(ptr, 0, sizeof(*ptr));
LIST_INIT(&(ptr->events));
LIST_INIT(&(ptr->links));
OTELC_RETURN();
}
/***
* NAME
* flt_otel_scope_data_free - scope data cleanup
*
* SYNOPSIS
* void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr)
*
* ARGUMENTS
* ptr - the scope data structure to free
*
* DESCRIPTION
* Frees all contents of the scope data structure pointed to by <ptr>: baggage
* and attribute key-value arrays, event entries with their attributes, link
* entries, and the status description string.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr)
{
struct flt_otel_scope_data_event *event, *event_back;
struct flt_otel_scope_data_link *link, *link_back;
OTELC_FUNC("%p", ptr);
if (ptr == NULL)
OTELC_RETURN();
FLT_OTEL_DBG_SCOPE_DATA("", ptr);
/* Destroy all dynamic scope data contents. */
otelc_kv_destroy(&(ptr->baggage.attr), ptr->baggage.cnt);
otelc_kv_destroy(&(ptr->attributes.attr), ptr->attributes.cnt);
list_for_each_entry_safe(event, event_back, &(ptr->events), list) {
otelc_kv_destroy(&(event->attr), event->cnt);
OTELC_SFREE(event->name);
OTELC_SFREE(event);
}
/* Free all resolved link entries. */
list_for_each_entry_safe(link, link_back, &(ptr->links), list)
OTELC_SFREE(link);
OTELC_SFREE(ptr->status.description);
(void)memset(ptr, 0, sizeof(*ptr));
OTELC_RETURN();
}
/***
* NAME
* flt_otel_scope_finish_mark - mark spans and contexts for finishing
*
* SYNOPSIS
* int flt_otel_scope_finish_mark(const struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len)
*
* ARGUMENTS
* rt_ctx - the runtime context containing spans and contexts
* id - the target name, or a wildcard ("*", "*req*", "*res*")
* id_len - length of the <id> string
*
* DESCRIPTION
* Marks spans and contexts for finishing. The <id> argument supports
* wildcards: "*" marks all spans and contexts, "*req*" marks the request
* channel only, "*res*" marks the response channel only. Otherwise, a named
* span or context is looked up by exact match.
*
* RETURN VALUE
* Returns the number of spans and contexts that were marked.
*/
int flt_otel_scope_finish_mark(const struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len)
{
struct flt_otel_scope_span *span;
struct flt_otel_scope_context *ctx;
int span_cnt = 0, ctx_cnt = 0, retval;
OTELC_FUNC("%p, \"%s\", %zu", rt_ctx, OTELC_STR_ARG(id), id_len);
/* Handle wildcard finish marks: all, request-only, response-only. */
if (FLT_OTEL_STR_CMP(FLT_OTEL_SCOPE_SPAN_FINISH_ALL, id)) {
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++;
}
OTELC_DBG(NOTICE, "marked %d span(s), %d context(s)", span_cnt, ctx_cnt);
}
else if (FLT_OTEL_STR_CMP(FLT_OTEL_SCOPE_SPAN_FINISH_REQ, id)) {
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;
ctx_cnt++;
}
OTELC_DBG(NOTICE, "marked REQuest channel %d span(s), %d context(s)", span_cnt, ctx_cnt);
}
else if (FLT_OTEL_STR_CMP(FLT_OTEL_SCOPE_SPAN_FINISH_RES, id)) {
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++;
}
OTELC_DBG(NOTICE, "marked RESponse channel %d span(s), %d context(s)", span_cnt, ctx_cnt);
}
else {
list_for_each_entry(span, &(rt_ctx->spans), list)
if (FLT_OTEL_CONF_STR_CMP(span->id, id)) {
span->flag_finish = 1;
span_cnt++;
break;
}
list_for_each_entry(ctx, &(rt_ctx->contexts), list)
if (FLT_OTEL_CONF_STR_CMP(ctx->id, id)) {
ctx->flag_finish = 1;
ctx_cnt++;
break;
}
if (span_cnt > 0)
OTELC_DBG(NOTICE, "marked span '%s'", id);
if (ctx_cnt > 0)
OTELC_DBG(NOTICE, "marked context '%s'", id);
if ((span_cnt + ctx_cnt) == 0)
OTELC_DBG(NOTICE, "cannot find span/context '%s'", id);
}
retval = span_cnt + ctx_cnt;
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_scope_finish_marked - finish marked spans and contexts
*
* SYNOPSIS
* void flt_otel_scope_finish_marked(const struct flt_otel_runtime_context *rt_ctx, const struct timespec *ts_finish)
*
* ARGUMENTS
* rt_ctx - the runtime context containing spans and contexts
* ts_finish - the monotonic timestamp to use as the span end time
*
* DESCRIPTION
* Ends all spans and destroys all contexts that have been marked for
* finishing by flt_otel_scope_finish_mark(). Each span is ended with the
* <ts_finish> timestamp; each context's OTel span context is destroyed.
* The finish flags are cleared after processing.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_scope_finish_marked(const struct flt_otel_runtime_context *rt_ctx, const struct timespec *ts_finish)
{
struct flt_otel_scope_span *span;
struct flt_otel_scope_context *ctx;
OTELC_FUNC("%p, %p", rt_ctx, ts_finish);
/* End all spans that have been marked for finishing. */
list_for_each_entry(span, &(rt_ctx->spans), list)
if (span->flag_finish) {
FLT_OTEL_DBG_SCOPE_SPAN("finishing span ", span);
OTELC_OPSR(span->span, end_with_options, ts_finish, OTELC_SPAN_STATUS_IGNORE, NULL);
span->flag_finish = 0;
}
/* Destroy all contexts that have been marked for finishing. */
list_for_each_entry(ctx, &(rt_ctx->contexts), list)
if (ctx->flag_finish) {
FLT_OTEL_DBG_SCOPE_CONTEXT("finishing context ", ctx);
if (ctx->context != NULL)
OTELC_OPSR(ctx->context, destroy);
ctx->flag_finish = 0;
}
OTELC_RETURN();
}
/***
* NAME
* flt_otel_scope_free_unused - remove unused spans and contexts
*
* SYNOPSIS
* void flt_otel_scope_free_unused(struct flt_otel_runtime_context *rt_ctx, struct channel *chn)
*
* ARGUMENTS
* rt_ctx - the runtime context to clean up
* chn - the channel for HTTP header cleanup
*
* DESCRIPTION
* Removes scope spans with a NULL OTel span and scope contexts with a NULL
* OTel context from the runtime context. For each removed context, the
* associated HTTP headers and HAProxy variables are also cleaned up via
* <chn>.
*
* RETURN VALUE
* This function does not return a value.
*/
void flt_otel_scope_free_unused(struct flt_otel_runtime_context *rt_ctx, struct channel *chn)
{
OTELC_FUNC("%p, %p", rt_ctx, chn);
if (rt_ctx == NULL)
OTELC_RETURN();
/* Remove spans that were never successfully created. */
if (!LIST_ISEMPTY(&(rt_ctx->spans))) {
struct flt_otel_scope_span *span, *span_back;
list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list)
if (span->span == NULL)
flt_otel_scope_span_free(&span);
}
/* Remove contexts that failed extraction and clean up their traces. */
if (!LIST_ISEMPTY(&(rt_ctx->contexts))) {
struct flt_otel_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_otel_http_headers_remove(chn, ctx->id, NULL);
#ifdef USE_OTEL_VARS
(void)flt_otel_vars_unset(rt_ctx->stream, FLT_OTEL_VARS_SCOPE, ctx->id, ctx->smp_opt_dir, NULL);
#endif
flt_otel_scope_context_free(&ctx);
}
}
FLT_OTEL_DBG_RUNTIME_CONTEXT("session context: ", rt_ctx);
OTELC_RETURN();
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,85 +0,0 @@
Comparison test configuration (cmp/)
====================================
The 'cmp' test is a simplified standalone configuration made for comparison with
other tracing implementations. It uses a reduced set of events and a compact
span hierarchy without context propagation, groups or metrics. This
configuration is closer to a typical production deployment.
All response-side scopes (http_response, http_response-error, server_session_end
and client_session_end) share the on-http-response event, which means they fire
in a single batch at response time.
Files
-----
cmp/otel.cfg OTel filter configuration (scopes)
cmp/haproxy.cfg HAProxy frontend/backend configuration
cmp/otel.yml Exporter, processor, reader and provider definitions
run-cmp.sh Convenience script to launch HAProxy with this config
Events
------
T = Trace (span)
This configuration produces traces only -- no metrics or log-records.
Request analyzer events:
Event Scope T
--------------------------------------------------------
on-client-session-start client_session_start x
on-frontend-tcp-request frontend_tcp_request x
on-frontend-http-request frontend_http_request x
on-backend-tcp-request backend_tcp_request x
on-backend-http-request backend_http_request x
on-server-unavailable server_unavailable x
Response analyzer events:
Event Scope T
--------------------------------------------------------
on-server-session-start server_session_start x
on-tcp-response tcp_response x
on-http-response http_response x
on-http-response http_response-error x (conditional)
on-http-response server_session_end - (finish only)
on-http-response client_session_end - (finish only)
The http_response-error scope fires conditionally when the ACL
acl-http-status-ok (status 100:399) does not match, setting an error status
on the "HTTP response" span.
The server_session_end and client_session_end scopes are bound to the
on-http-response event and only perform finish operations.
Span hierarchy
--------------
"HAProxy session" (root)
+-- "Client session"
+-- "Frontend TCP request"
+-- "Frontend HTTP request"
+-- "Backend TCP request"
+-- "Backend HTTP request"
"HAProxy session" (root)
+-- "Server session"
+-- "TCP response"
+-- "HTTP response"
Running the test
----------------
From the test/ directory:
% ./run-cmp.sh [/path/to/haproxy] [pidfile]
If no arguments are given, the script looks for the haproxy binary three
directories up from the current working directory. The backend origin server
must be running on 127.0.0.1:8000.

View file

@ -1,149 +0,0 @@
Context propagation test configuration (ctx/)
=============================================
The 'ctx' test is a standalone configuration that uses inject/extract context
propagation on every scope. Spans are opened using extracted span contexts
stored in HAProxy variables as parent references instead of direct span names.
This adds the overhead of context serialization, variable storage and
deserialization on every scope execution.
The event coverage matches the 'sa' configuration. The key difference is the
propagation mechanism: each scope injects its context into a numbered variable
(otel_ctx_1 through otel_ctx_17) and the next scope extracts from that variable
to establish the parent relationship.
The client_session_start event is split into two scopes (client_session_start_1
and client_session_start_2) to demonstrate inject/extract between scopes
handling the same event.
Files
-----
ctx/otel.cfg OTel filter configuration (scopes, groups, contexts)
ctx/haproxy.cfg HAProxy frontend/backend configuration
ctx/otel.yml Exporter, processor, reader and provider definitions
run-ctx.sh Convenience script to launch HAProxy with this config
Events
------
T = Trace (span)
This configuration produces traces only -- no metrics or log-records.
Stream lifecycle events:
Event Scope T
----------------------------------------------------------
on-client-session-start client_session_start_1 x
on-client-session-start client_session_start_2 x
Request analyzer events:
Event Scope T
--------------------------------------------------------------------
on-frontend-tcp-request frontend_tcp_request x
on-http-wait-request http_wait_request x
on-http-body-request http_body_request x
on-frontend-http-request frontend_http_request x
on-switching-rules-request switching_rules_request x
on-backend-tcp-request backend_tcp_request x
on-backend-http-request backend_http_request x
on-process-server-rules-request process_server_rules_request x
on-http-process-request http_process_request x
on-tcp-rdp-cookie-request tcp_rdp_cookie_request x
on-process-sticking-rules-request process_sticking_rules_request x
on-client-session-end client_session_end -
on-server-unavailable server_unavailable -
Response analyzer events:
Event Scope T
--------------------------------------------------------------------
on-server-session-start server_session_start x
on-tcp-response tcp_response x
on-http-wait-response http_wait_response x
on-process-store-rules-response process_store_rules_response x
on-http-response http_response x
on-http-response http_response-error x (conditional)
on-server-session-end server_session_end -
The http_response_group (http_response_1, http_response_2) and
http_after_response_group (http_after_response) are invoked via http-response
and http-after-response directives in haproxy.cfg.
Context propagation chain
-------------------------
Each scope injects its span context into a HAProxy variable and the next scope
extracts it. The variable names and their flow:
otel_ctx_1 "HAProxy session" -> client_session_start_2
otel_ctx_2 "Client session" -> frontend_tcp_request
otel_ctx_3 "Frontend TCP request" -> http_wait_request
otel_ctx_4 "HTTP wait request" -> http_body_request
otel_ctx_5 "HTTP body request" -> frontend_http_request
otel_ctx_6 "Frontend HTTP request" -> switching_rules_request
otel_ctx_7 "Switching rules request" -> backend_tcp_request
otel_ctx_8 "Backend TCP request" -> backend_http_request
otel_ctx_9 "Backend HTTP request" -> process_server_rules_request
otel_ctx_10 "Process server rules request" -> http_process_request
otel_ctx_11 "HTTP process request" -> tcp_rdp_cookie_request
otel_ctx_12 "TCP RDP cookie request" -> process_sticking_rules_request
otel_ctx_13 "Process sticking rules req." -> server_session_start
otel_ctx_14 "Server session" -> tcp_response
otel_ctx_15 "TCP response" -> http_wait_response
otel_ctx_16 "HTTP wait response" -> process_store_rules_response
otel_ctx_17 "Process store rules response" -> http_response
All contexts use both use-headers and use-vars injection modes, except
otel_ctx_14 and otel_ctx_15 which use use-vars only.
Span hierarchy
--------------
The span hierarchy is identical to the 'sa' configuration, but parent
relationships are established through extracted contexts rather than direct
span name references.
Request path:
"HAProxy session" (root) [otel_ctx_1]
+-- "Client session" [otel_ctx_2]
+-- "Frontend TCP request" [otel_ctx_3]
+-- "HTTP wait request" [otel_ctx_4]
+-- "HTTP body request" [otel_ctx_5]
+-- "Frontend HTTP request" [otel_ctx_6]
+-- "Switching rules request" [otel_ctx_7]
+-- "Backend TCP request" [otel_ctx_8]
+-- (continues to process_sticking_rules_request)
Response path:
"HAProxy session" [otel_ctx_1]
+-- "Server session" [otel_ctx_14]
+-- "TCP response" [otel_ctx_15]
+-- "HTTP wait response" [otel_ctx_16]
+-- "Process store rules response" [otel_ctx_17]
+-- "HTTP response"
Auxiliary spans:
"HAProxy session"
+-- "HAProxy response" (http_after_response_group, on error)
Running the test
----------------
From the test/ directory:
% ./run-ctx.sh [/path/to/haproxy] [pidfile]
If no arguments are given, the script looks for the haproxy binary three
directories up from the current working directory. The backend origin server
must be running on 127.0.0.1:8000.

View file

@ -1,53 +0,0 @@
Empty test configuration (empty/)
=================================
The 'empty' test is a minimal configuration that loads the OTel filter without
defining any scopes, events or groups. The instrumentation block contains only
the config directive pointing to the YAML pipeline definition.
This configuration verifies that the filter initializes and shuts down cleanly
when no telemetry is configured. It exercises the full YAML parsing path
(exporters, processors, readers, samplers, providers and signals) without
producing any trace, metric or log-record data.
Files
-----
empty/otel.cfg OTel filter configuration (instrumentation only)
empty/haproxy.cfg HAProxy frontend/backend configuration
empty/otel.yml Exporter, processor, reader and provider definitions
Events
------
No events are registered. The filter is loaded and attached to the frontend
but performs no per-stream processing.
YAML pipeline
-------------
Despite the empty filter configuration, the otel.yml file defines a complete
pipeline with all three signal types to verify that the YAML parser handles
the full configuration without errors:
Signal Exporter Processor / Reader
-----------------------------------------------------------
traces exporter_traces_otlp_http processor_traces_batch
metrics exporter_metrics_otlp_http reader_metrics
logs exporter_logs_otlp_http processor_logs_batch
Additional exporter definitions (otlp_file, otlp_grpc, ostream, memory,
zipkin, elasticsearch) are present in the YAML but are not wired into the
active signal pipelines.
Running the test
----------------
There is no dedicated run script for the empty configuration. To run it
manually from the test/ directory:
% /path/to/haproxy -f haproxy-common.cfg -f empty/haproxy.cfg

View file

@ -1,124 +0,0 @@
Frontend / backend test configuration (fe/ + be/)
=================================================
The 'fe-be' test uses two cascaded HAProxy instances to demonstrate
inter-process trace context propagation via HTTP headers. The frontend instance
(fe/) creates the root trace and injects span context into the HTTP request
headers. The backend instance (be/) extracts that context and continues the
trace as a child of the frontend's span.
The two instances run as separate processes: the frontend listens on port 10080
and proxies to the backend on port 11080, which in turn proxies to the origin
server on port 8000.
Files
-----
fe/otel.cfg OTel filter configuration for the frontend instance
fe/haproxy.cfg HAProxy configuration for the frontend instance
be/otel.cfg OTel filter configuration for the backend instance
be/haproxy.cfg HAProxy configuration for the backend instance
run-fe-be.sh Convenience script to launch both instances
Events
------
T = Trace (span)
Both instances produce traces only -- no metrics or log-records.
Frontend (fe/) events:
Event Scope T
--------------------------------------------------------
on-client-session-start client_session_start x
on-frontend-tcp-request frontend_tcp_request x
on-frontend-http-request frontend_http_request x
on-backend-tcp-request backend_tcp_request x
on-backend-http-request backend_http_request x
on-client-session-end client_session_end -
on-server-session-start server_session_start x
on-tcp-response tcp_response x
on-http-response http_response x
on-server-session-end server_session_end -
Backend (be/) events:
Event Scope T
--------------------------------------------------------
on-frontend-http-request frontend_http_request x
on-backend-tcp-request backend_tcp_request x
on-backend-http-request backend_http_request x
on-client-session-end client_session_end -
on-server-session-start server_session_start x
on-tcp-response tcp_response x
on-http-response http_response x
on-server-session-end server_session_end -
The backend starts its trace at on-frontend-http-request where it extracts
the span context injected by the frontend. Earlier request events
(on-client-session-start, on-frontend-tcp-request) are not needed because
the context is not yet available in the HTTP headers at that point.
Context propagation
-------------------
The frontend injects context into HTTP headers in the backend_http_request
scope:
span "HAProxy session"
inject "otel-ctx" use-headers
The backend extracts that context in its frontend_http_request scope:
extract "otel-ctx" use-headers
span "HAProxy session" parent "otel-ctx" root
Span hierarchy
--------------
Frontend (fe/):
"HAProxy session" (root)
+-- "Client session"
+-- "Frontend TCP request"
+-- "Frontend HTTP request"
+-- "Backend TCP request"
+-- "Backend HTTP request"
"HAProxy session" (root)
+-- "Server session"
+-- "TCP response"
+-- "HTTP response"
Backend (be/):
"HAProxy session" (root, parent: frontend's "HAProxy session")
+-- "Client session"
+-- "Frontend HTTP request"
+-- "Backend TCP request"
+-- "Backend HTTP request"
"HAProxy session" (root)
+-- "Server session"
+-- "TCP response"
+-- "HTTP response"
Running the test
----------------
From the test/ directory:
% ./run-fe-be.sh [/path/to/haproxy] [pidfile]
If no arguments are given, the script looks for the haproxy binary three
directories up from the current working directory. The backend origin server
must be running on 127.0.0.1:8000.
The script launches both HAProxy instances in the background and waits.
Press CTRL-C to stop both instances.

View file

@ -1,158 +0,0 @@
Full event coverage test configuration (full/)
==============================================
The 'full' test is a standalone single-instance configuration that exercises
every supported OTel filter event with all three signal types: traces (spans),
metrics (instruments) and logs (log-records).
It extends the 'sa' (standalone) configuration by adding the events that 'sa'
does not cover and by attaching log-records to every scope.
Files
-----
full/otel.cfg OTel filter configuration (scopes, groups, instruments)
full/haproxy.cfg HAProxy frontend/backend configuration
full/otel.yml Exporter, processor, reader and provider definitions
run-full.sh Convenience script to launch HAProxy with this config
Events
------
The table below lists every event defined in include/event.h together with the
scope that handles it and the signals produced by that scope.
T = Trace (span) M = Metric (instrument) L = Log (log-record)
Stream lifecycle events:
Event Scope T M L
---------------------------------------------------------------
on-stream-start on_stream_start x x x
on-stream-stop on_stream_stop - - x
on-idle-timeout on_idle_timeout x x x
on-backend-set on_backend_set x x x
Request analyzer events:
Event Scope T M L
--------------------------------------------------------------------------
on-client-session-start client_session_start x x x
on-frontend-tcp-request frontend_tcp_request x x x
on-http-wait-request http_wait_request x - x
on-http-body-request http_body_request x - x
on-frontend-http-request frontend_http_request x x x
on-switching-rules-request switching_rules_request x - x
on-backend-tcp-request backend_tcp_request x x x
on-backend-http-request backend_http_request x - x
on-process-server-rules-request process_server_rules_request x - x
on-http-process-request http_process_request x - x
on-tcp-rdp-cookie-request tcp_rdp_cookie_request x - x
on-process-sticking-rules-request process_sticking_rules_request x - x
on-http-headers-request http_headers_request x x x
on-http-end-request http_end_request x x x
on-client-session-end client_session_end - x x
on-server-unavailable server_unavailable - - x
Response analyzer events:
Event Scope T M L
--------------------------------------------------------------------------
on-server-session-start server_session_start x x x
on-tcp-response tcp_response x x x
on-http-wait-response http_wait_response x - x
on-process-store-rules-response process_store_rules_response x - x
on-http-response http_response x x x
on-http-headers-response http_headers_response x x x
on-http-end-response http_end_response x x x
on-http-reply http_reply x x x
on-server-session-end server_session_end - x x
Additionally, the http_response-error scope fires conditionally on the
on-http-response event when the response status is outside the 100-399 range,
setting an error status on the "HTTP response" span.
The http_response_group (http_response_1, http_response_2) and
http_after_response_group (http_after_response) are invoked via http-response
and http-after-response directives in haproxy.cfg.
Instruments
-----------
Every instrument definition has at least one corresponding update.
Instrument name Type Defined in Updated in
-------------------------------------------------------------------------------
haproxy.sessions.active udcnt_int on_stream_start client_session_end
haproxy.fe.connections gauge_int on_stream_start http_response
idle.count cnt_int on_idle_timeout on_idle_timeout
haproxy.backend.set cnt_int on_backend_set on_backend_set
haproxy.client.session.start cnt_int client_session_start client_session_end
haproxy.tcp.request.fe cnt_int frontend_tcp_request frontend_http_request
haproxy.http.requests cnt_int frontend_http_request http_response
haproxy.http.latency hist_int frontend_http_request frontend_http_request,
http_response
haproxy.tcp.request.be cnt_int backend_tcp_request backend_http_request
haproxy.http.headers.request cnt_int http_headers_request http_end_request
haproxy.http.end.request cnt_int http_end_request client_session_end
haproxy.server.session.start cnt_int server_session_start server_session_end
haproxy.tcp.response cnt_int tcp_response http_wait_response
haproxy.http.headers.response cnt_int http_headers_response http_end_response
haproxy.http.end.response cnt_int http_end_response http_reply
haproxy.http.reply cnt_int http_reply server_session_end
Span hierarchy
--------------
Request path:
"HAProxy session" (root)
+-- "Client session"
+-- "Frontend TCP request"
+-- "HTTP wait request"
+-- "HTTP body request"
+-- "Frontend HTTP request" [link: "HAProxy session"]
+-- "Switching rules request"
+-- "Backend TCP request"
+-- "Backend HTTP request"
+-- "Process server rules request"
+-- "HTTP process request"
+-- "TCP RDP cookie request"
+-- "Process sticking rules request"
+-- "HTTP headers request"
+-- "HTTP end request"
Response path:
"HAProxy session" (root)
+-- "Server session" [link: "HAProxy session", "Client session"]
+-- "TCP response"
+-- "HTTP wait response"
+-- "Process store rules response"
+-- "HTTP response"
+-- "HTTP headers response"
+-- "HTTP end response"
+-- "HTTP reply"
Auxiliary spans:
"HAProxy session"
+-- "Backend set"
+-- "heartbeat" (on-idle-timeout, periodic)
+-- "HAProxy response" (http_after_response_group, on error)
Running the test
----------------
From the test/ directory:
% ./run-full.sh [/path/to/haproxy] [pidfile]
If no arguments are given, the script looks for the haproxy binary three
directories up from the current working directory. The backend origin server
must be running on 127.0.0.1:8000.

View file

@ -1,134 +0,0 @@
Standalone test configuration (sa/)
=====================================
The 'sa' test is a standalone single-instance configuration that
exercises most HAProxy filter events with spans, attributes, events,
links, baggage, status, metrics, logs and groups. It represents the
most comprehensive single-instance configuration and is used as the
worst-case scenario in speed tests.
Six events are not covered by this configuration: on-backend-set,
on-http-headers-request, on-http-end-request, on-http-headers-response,
on-http-end-response and on-http-reply. The 'full' configuration
extends 'sa' with those events.
Files
------
sa/otel.cfg OTel filter configuration (scopes, groups, instruments)
sa/haproxy.cfg HAProxy frontend/backend configuration
sa/otel.yml Exporter, processor, reader and provider definitions
run-sa.sh Convenience script to launch HAProxy with this config
Events
-------
T = Trace (span) M = Metric (instrument) L = Log (log-record)
Stream lifecycle events:
Event Scope T M L
---------------------------------------------------------------
on-stream-start on_stream_start x x x
on-stream-stop on_stream_stop - - -
on-idle-timeout on_idle_timeout x x x
Request analyzer events:
Event Scope T M L
--------------------------------------------------------------------------
on-client-session-start client_session_start x - -
on-frontend-tcp-request frontend_tcp_request x - -
on-http-wait-request http_wait_request x - -
on-http-body-request http_body_request x - -
on-frontend-http-request frontend_http_request x x x
on-switching-rules-request switching_rules_request x - -
on-backend-tcp-request backend_tcp_request x - -
on-backend-http-request backend_http_request x - -
on-process-server-rules-request process_server_rules_request x - -
on-http-process-request http_process_request x - -
on-tcp-rdp-cookie-request tcp_rdp_cookie_request x - -
on-process-sticking-rules-request process_sticking_rules_request x - -
on-client-session-end client_session_end - x -
on-server-unavailable server_unavailable - - -
Response analyzer events:
Event Scope T M L
--------------------------------------------------------------------------
on-server-session-start server_session_start x - -
on-tcp-response tcp_response x - -
on-http-wait-response http_wait_response x - -
on-process-store-rules-response process_store_rules_response x - -
on-http-response http_response x x -
on-server-session-end server_session_end - - -
Additionally, the http_response-error scope fires conditionally on the
on-http-response event when the response status is outside the 100-399
range, setting an error status on the "HTTP response" span.
The http_response_group (http_response_1, http_response_2) and
http_after_response_group (http_after_response) are invoked via
http-response and http-after-response directives in haproxy.cfg.
Instruments
------------
Instrument name Type Defined in Updated in
--------------------------------------------------------------------------
haproxy.sessions.active udcnt_int on_stream_start client_session_end
haproxy.fe.connections gauge_int on_stream_start http_response
idle.count cnt_int on_idle_timeout on_idle_timeout
haproxy.http.requests cnt_int frontend_http_request http_response
haproxy.http.latency hist_int frontend_http_request frontend_http_request,
http_response
Span hierarchy
---------------
Request path:
"HAProxy session" (root)
+-- "Client session"
+-- "Frontend TCP request"
+-- "HTTP wait request"
+-- "HTTP body request"
+-- "Frontend HTTP request" [link: "HAProxy session"]
+-- "Switching rules request"
+-- "Backend TCP request"
+-- "Backend HTTP request"
+-- "Process server rules request"
+-- "HTTP process request"
+-- "TCP RDP cookie request"
+-- "Process sticking rules request"
Response path:
"HAProxy session" (root)
+-- "Server session" [link: "HAProxy session", "Client session"]
+-- "TCP response"
+-- "HTTP wait response"
+-- "Process store rules response"
+-- "HTTP response"
Auxiliary spans:
"HAProxy session"
+-- "heartbeat" (on-idle-timeout, periodic)
+-- "HAProxy response" (http_after_response_group, on error)
Running the test
-----------------
From the test/ directory:
% ./run-sa.sh [/path/to/haproxy] [pidfile]
If no arguments are given, the script looks for the haproxy binary three
directories up from the current working directory. The backend origin
server must be running on 127.0.0.1:8000.

View file

@ -1,144 +0,0 @@
--- rate-limit 100.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 182.58us 129.11us 16.19ms 98.11%
Req/Sec 5.63k 240.83 6.29k 69.74%
Latency Distribution
50% 169.00us
75% 183.00us
90% 209.00us
99% 367.00us
13438310 requests in 5.00m, 3.24GB read
Requests/sec: 44779.51
Transfer/sec: 11.06MB
----------------------------------------------------------------------
--- rate-limit 75.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 180.10us 122.51us 14.57ms 98.00%
Req/Sec 5.70k 253.08 6.28k 70.41%
Latency Distribution
50% 169.00us
75% 184.00us
90% 206.00us
99% 362.00us
13613023 requests in 5.00m, 3.28GB read
Requests/sec: 45361.63
Transfer/sec: 11.20MB
----------------------------------------------------------------------
--- rate-limit 50.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 176.59us 125.34us 15.58ms 98.02%
Req/Sec 5.81k 230.84 6.42k 72.14%
Latency Distribution
50% 166.00us
75% 182.00us
90% 202.00us
99% 361.00us
13888448 requests in 5.00m, 3.35GB read
Requests/sec: 46279.45
Transfer/sec: 11.43MB
----------------------------------------------------------------------
--- rate-limit 25.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 173.57us 118.35us 13.12ms 97.91%
Req/Sec 5.91k 257.69 6.46k 66.83%
Latency Distribution
50% 162.00us
75% 178.00us
90% 199.00us
99% 362.00us
14122906 requests in 5.00m, 3.41GB read
Requests/sec: 47060.71
Transfer/sec: 11.62MB
----------------------------------------------------------------------
--- rate-limit 10.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 170.85us 112.24us 10.72ms 97.84%
Req/Sec 6.00k 269.81 8.10k 69.46%
Latency Distribution
50% 159.00us
75% 172.00us
90% 194.00us
99% 361.00us
14342642 requests in 5.00m, 3.46GB read
Requests/sec: 47792.96
Transfer/sec: 11.80MB
----------------------------------------------------------------------
--- rate-limit 2.5 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 169.11us 127.52us 16.90ms 98.06%
Req/Sec 6.08k 261.57 6.55k 67.30%
Latency Distribution
50% 158.00us
75% 168.00us
90% 186.00us
99% 367.00us
14527714 requests in 5.00m, 3.50GB read
Requests/sec: 48409.62
Transfer/sec: 11.96MB
----------------------------------------------------------------------
--- rate-limit 0.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 168.09us 108.96us 9.07ms 97.81%
Req/Sec 6.10k 284.22 6.55k 70.65%
Latency Distribution
50% 157.00us
75% 167.00us
90% 184.00us
99% 362.00us
14580762 requests in 5.00m, 3.52GB read
Requests/sec: 48586.42
Transfer/sec: 12.00MB
----------------------------------------------------------------------
--- rate-limit disabled --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 168.57us 118.05us 13.51ms 97.94%
Req/Sec 6.09k 251.47 6.99k 67.33%
Latency Distribution
50% 158.00us
75% 167.00us
90% 184.00us
99% 363.00us
14557824 requests in 5.00m, 3.51GB read
Requests/sec: 48509.96
Transfer/sec: 11.98MB
----------------------------------------------------------------------
--- rate-limit off --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 168.64us 120.15us 14.49ms 98.01%
Req/Sec 6.09k 267.94 6.57k 66.19%
Latency Distribution
50% 158.00us
75% 167.00us
90% 184.00us
99% 361.00us
14551312 requests in 5.00m, 3.51GB read
Requests/sec: 48488.23
Transfer/sec: 11.98MB
----------------------------------------------------------------------

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