Compare commits

..

No commits in common. "master" and "v3.2-dev16" have entirely different histories.

962 changed files with 23966 additions and 85719 deletions

View file

@ -1,7 +1,7 @@
FreeBSD_task: FreeBSD_task:
freebsd_instance: freebsd_instance:
matrix: matrix:
image_family: freebsd-14-3 image_family: freebsd-14-2
only_if: $CIRRUS_BRANCH =~ 'master|next' only_if: $CIRRUS_BRANCH =~ 'master|next'
install_script: 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 lua54 socat pcre2

View file

@ -1,48 +0,0 @@
name: 'setup VTest'
description: 'ssss'
runs:
using: "composite"
steps:
- name: Setup coredumps
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
shell: bash
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: bash
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: bash
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: bash
run: |
DESTDIR=${{ github.workspace }}/vtest scripts/build-vtest.sh
- name: Install problem matcher for VTest
shell: bash
# This allows one to more easily see which tests fail.
run: echo "::add-matcher::.github/vtest.json"

View file

@ -19,7 +19,7 @@ defaults
frontend h2 frontend h2
mode http mode http
bind 127.0.0.1:8443 ssl crt reg-tests/ssl/certs/common.pem alpn h2,http/1.1 bind 127.0.0.1:8443 ssl crt reg-tests/ssl/common.pem alpn h2,http/1.1
default_backend h2b default_backend h2b
backend h2b backend h2b

167
.github/matrix.py vendored
View file

@ -12,7 +12,6 @@ import functools
import json import json
import re import re
import sys import sys
import urllib.error
import urllib.request import urllib.request
from os import environ from os import environ
from packaging import version from packaging import version
@ -20,10 +19,9 @@ from packaging import version
# #
# this CI is used for both development and stable branches of HAProxy # 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 # "haproxy-" - stable branches
# "vX.Y.Z" - release tags
# otherwise - development branch (i.e. "latest" ssl variants, "latest" github images) # otherwise - development branch (i.e. "latest" ssl variants, "latest" github images)
# #
@ -34,24 +32,13 @@ def get_all_github_tags(url):
headers = {} headers = {}
if environ.get("GITHUB_TOKEN") is not None: if environ.get("GITHUB_TOKEN") is not None:
headers["Authorization"] = "token {}".format(environ.get("GITHUB_TOKEN")) headers["Authorization"] = "token {}".format(environ.get("GITHUB_TOKEN"))
all_tags = [] request = urllib.request.Request(url, headers=headers)
page = 1 try:
sep = "&" if "?" in url else "?" tags = urllib.request.urlopen(request)
while True: except:
paginated_url = "{}{}per_page=100&page={}".format(url, sep, page) return None
request = urllib.request.Request(paginated_url, headers=headers) tags = json.loads(tags.read().decode("utf-8"))
try: return [tag['name'] for tag in tags]
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
@functools.lru_cache(5) @functools.lru_cache(5)
def determine_latest_openssl(ssl): 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('.'))) return tuple(map(int, version_string[1:].split('.')))
def aws_lc_version_valid(version_string): 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) @functools.lru_cache(5)
def determine_latest_aws_lc(ssl): def determine_latest_aws_lc(ssl):
@ -77,8 +64,6 @@ def determine_latest_aws_lc(ssl):
if not tags: if not tags:
return "AWS_LC_VERSION=failed_to_detect" return "AWS_LC_VERSION=failed_to_detect"
valid_tags = list(filter(aws_lc_version_valid, tags)) 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) latest_tag = max(valid_tags, key=aws_lc_version_string_to_num)
return "AWS_LC_VERSION={}".format(latest_tag[1:]) return "AWS_LC_VERSION={}".format(latest_tag[1:])
@ -86,16 +71,15 @@ def aws_lc_fips_version_string_to_num(version_string):
return tuple(map(int, version_string[12:].split('.'))) return tuple(map(int, version_string[12:].split('.')))
def aws_lc_fips_version_valid(version_string): def aws_lc_fips_version_valid(version_string):
return re.match(r'^AWS-LC-FIPS-[0-9]+(\.[0-9]+)*$', version_string) return re.match('^AWS-LC-FIPS-[0-9]+(\.[0-9]+)*$', version_string)
@functools.lru_cache(5) @functools.lru_cache(5)
def determine_latest_aws_lc_fips(ssl): def determine_latest_aws_lc_fips(ssl):
tags = get_all_github_tags("https://api.github.com/repos/aws/aws-lc/tags") # the AWS-LC-FIPS tags are at the end of the list, so let's get a lot
tags = get_all_github_tags("https://api.github.com/repos/aws/aws-lc/tags?per_page=200")
if not tags: if not tags:
return "AWS_LC_FIPS_VERSION=failed_to_detect" return "AWS_LC_FIPS_VERSION=failed_to_detect"
valid_tags = list(filter(aws_lc_fips_version_valid, tags)) 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) latest_tag = max(valid_tags, key=aws_lc_fips_version_string_to_num)
return "AWS_LC_FIPS_VERSION={}".format(latest_tag[12:]) return "AWS_LC_FIPS_VERSION={}".format(latest_tag[12:])
@ -103,7 +87,7 @@ def wolfssl_version_string_to_num(version_string):
return tuple(map(int, version_string[1:].removesuffix('-stable').split('.'))) return tuple(map(int, version_string[1:].removesuffix('-stable').split('.')))
def wolfssl_version_valid(version_string): def wolfssl_version_valid(version_string):
return re.match(r'^v[0-9]+(\.[0-9]+)*-stable$', version_string) return re.match('^v[0-9]+(\.[0-9]+)*-stable$', version_string)
@functools.lru_cache(5) @functools.lru_cache(5)
def determine_latest_wolfssl(ssl): def determine_latest_wolfssl(ssl):
@ -136,18 +120,14 @@ def clean_compression(compression):
def main(ref_name): def main(ref_name):
print("Generating matrix for branch '{}'.".format(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 = [] matrix = []
# Ubuntu # Ubuntu
if is_stable: if "haproxy-" in ref_name:
os = "ubuntu-24.04" # stable branch os = "ubuntu-22.04" # stable branch
os_arm = "ubuntu-24.04-arm" # stable branch
else: else:
os = "ubuntu-24.04" # development branch os = "ubuntu-24.04" # development branch
os_arm = "ubuntu-24.04-arm" # development branch
TARGET = "linux-glibc" TARGET = "linux-glibc"
for CC in ["gcc", "clang"]: for CC in ["gcc", "clang"]:
@ -192,37 +172,36 @@ def main(ref_name):
# ASAN # ASAN
for os_asan in [os, os_arm]: matrix.append(
matrix.append( {
{ "name": "{}, {}, ASAN, all features".format(os, CC),
"name": "{}, {}, ASAN, all features".format(os_asan, CC), "os": os,
"os": os_asan, "TARGET": TARGET,
"TARGET": TARGET, "CC": CC,
"CC": CC, "FLAGS": [
"FLAGS": [ "USE_OBSOLETE_LINKER=1",
"USE_OBSOLETE_LINKER=1", 'ARCH_FLAGS="-g -fsanitize=address"',
'ARCH_FLAGS="-g -fsanitize=address"', 'OPT_CFLAGS="-O1"',
'OPT_CFLAGS="-O1"', "USE_ZLIB=1",
"USE_ZLIB=1", "USE_OT=1",
"USE_OT=1", "OT_INC=${HOME}/opt-ot/include",
"OT_INC=${HOME}/opt-ot/include", "OT_LIB=${HOME}/opt-ot/lib",
"OT_LIB=${HOME}/opt-ot/lib", "OT_RUNPATH=1",
"OT_RUNPATH=1", "USE_PCRE2=1",
"USE_PCRE2=1", "USE_PCRE2_JIT=1",
"USE_PCRE2_JIT=1", "USE_LUA=1",
"USE_LUA=1", "USE_OPENSSL=1",
"USE_OPENSSL=1", "USE_WURFL=1",
"USE_WURFL=1", "WURFL_INC=addons/wurfl/dummy",
"WURFL_INC=addons/wurfl/dummy", "WURFL_LIB=addons/wurfl/dummy",
"WURFL_LIB=addons/wurfl/dummy", "USE_DEVICEATLAS=1",
"USE_DEVICEATLAS=1", "DEVICEATLAS_SRC=addons/deviceatlas/dummy",
"DEVICEATLAS_SRC=addons/deviceatlas/dummy", "USE_PROMEX=1",
"USE_PROMEX=1", "USE_51DEGREES=1",
"USE_51DEGREES=1", "51DEGREES_SRC=addons/51degrees/dummy/pattern",
"51DEGREES_SRC=addons/51degrees/dummy/pattern", ],
], }
} )
)
for compression in ["USE_ZLIB=1"]: for compression in ["USE_ZLIB=1"]:
matrix.append( matrix.append(
@ -239,14 +218,13 @@ def main(ref_name):
"stock", "stock",
"OPENSSL_VERSION=1.0.2u", "OPENSSL_VERSION=1.0.2u",
"OPENSSL_VERSION=1.1.1s", "OPENSSL_VERSION=1.1.1s",
"OPENSSL_VERSION=3.5.1", "QUICTLS=yes",
"QUICTLS_VERSION=OpenSSL_1_1_1w-quic1",
"WOLFSSL_VERSION=5.7.0", "WOLFSSL_VERSION=5.7.0",
"AWS_LC_VERSION=1.39.0", "AWS_LC_VERSION=1.39.0",
# "BORINGSSL=yes", # "BORINGSSL=yes",
] ]
if not is_stable: # development branch if "haproxy-" not in ref_name: # development branch
ssl_versions = ssl_versions + [ ssl_versions = ssl_versions + [
"OPENSSL_VERSION=latest", "OPENSSL_VERSION=latest",
"LIBRESSL_VERSION=latest", "LIBRESSL_VERSION=latest",
@ -254,7 +232,8 @@ def main(ref_name):
for ssl in ssl_versions: for ssl in ssl_versions:
flags = ["USE_OPENSSL=1"] 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: if "WOLFSSL" in ssl:
flags.append("USE_OPENSSL_WOLFSSL=1") flags.append("USE_OPENSSL_WOLFSSL=1")
if "AWS_LC" in ssl: if "AWS_LC" in ssl:
@ -264,23 +243,8 @@ def main(ref_name):
flags.append("SSL_INC=${HOME}/opt/include") flags.append("SSL_INC=${HOME}/opt/include")
if "LIBRESSL" in ssl and "latest" in ssl: if "LIBRESSL" in ssl and "latest" in ssl:
ssl = determine_latest_libressl(ssl) ssl = determine_latest_libressl(ssl)
skipdup=1
if "OPENSSL" in ssl and "latest" in ssl: if "OPENSSL" in ssl and "latest" in ssl:
ssl = determine_latest_openssl(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( matrix.append(
{ {
@ -293,21 +257,24 @@ def main(ref_name):
} }
) )
# macOS on dev branches # macOS
if not is_stable:
os = "macos-26" # development branch
TARGET = "osx" if "haproxy-" in ref_name:
for CC in ["clang"]: os = "macos-13" # stable branch
matrix.append( else:
{ os = "macos-15" # development branch
"name": "{}, {}, no features".format(os, CC),
"os": os, TARGET = "osx"
"TARGET": TARGET, for CC in ["clang"]:
"CC": CC, matrix.append(
"FLAGS": [], {
} "name": "{}, {}, no features".format(os, CC),
) "os": os,
"TARGET": TARGET,
"CC": CC,
"FLAGS": [],
}
)
# Print matrix # Print matrix

View file

@ -1,8 +1,8 @@
name: openssl master name: AWS-LC-FIPS
on: on:
schedule: schedule:
- cron: "0 3 * * *" - cron: "0 0 * * 4"
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@ -13,19 +13,33 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: 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 "from matrix import determine_latest_aws_lc_fips; print(determine_latest_aws_lc_fips(''))")
echo $result
echo "result=$result" >> $GITHUB_OUTPUT
- name: Cache AWS-LC
id: cache_aws_lc
uses: actions/cache@v4
with:
path: '~/opt/'
key: ssl-${{ steps.get_aws_lc_release.outputs.result }}-Ubuntu-latest-gcc
- name: Install apt dependencies - name: Install apt dependencies
run: | run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none 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 socat gdb jose
sudo apt-get --no-install-recommends -y install libpsl-dev - name: Install AWS-LC
- uses: ./.github/actions/setup-vtest if: ${{ steps.cache_ssl.outputs.cache-hit != 'true' }}
- name: Install OpenSSL master run: env ${{ steps.get_aws_lc_release.outputs.result }} scripts/build-ssl.sh
run: env OPENSSL_VERSION="git-master" GIT_TYPE="branch" scripts/build-ssl.sh
- name: Compile HAProxy - name: Compile HAProxy
run: | run: |
make -j$(nproc) ERR=1 CC=gcc TARGET=linux-glibc \ make -j$(nproc) ERR=1 CC=gcc TARGET=linux-glibc \
USE_QUIC=1 USE_OPENSSL=1 \ USE_OPENSSL_AWSLC=1 USE_QUIC=1 \
SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include \ SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include \
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \ DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/" ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/"
@ -35,7 +49,7 @@ jobs:
run: | run: |
ldd $(which haproxy) ldd $(which haproxy)
haproxy -vv 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 - name: Install problem matcher for VTest
run: echo "::add-matcher::.github/vtest.json" run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest for HAProxy - name: Run VTest for HAProxy
@ -46,7 +60,11 @@ jobs:
ulimit -n 65536 ulimit -n 65536
# allow to catch coredumps # allow to catch coredumps
ulimit -c unlimited ulimit -c unlimited
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: Run Unit tests
id: unittests
run: |
make unit-tests
- name: Show VTest results - name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }} if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: | run: |
@ -57,10 +75,6 @@ jobs:
echo "::endgroup::" echo "::endgroup::"
done done
exit 1 exit 1
- name: Run Unit tests
id: unittests
run: |
make unit-tests
- name: Show coredumps - name: Show coredumps
if: ${{ failure() && steps.vtest.outcome == 'failure' }} if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: | run: |
@ -75,3 +89,13 @@ jobs:
if [ "$failed" = true ]; then if [ "$failed" = true ]; then
exit 1; exit 1;
fi 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

@ -9,28 +9,23 @@ permissions:
contents: read contents: read
jobs: jobs:
Test: test:
name: ${{ matrix.name }}
runs-on: ubuntu-latest 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' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install VTest
run: |
scripts/build-vtest.sh
- name: Determine latest AWS-LC release - name: Determine latest AWS-LC release
id: get_aws_lc_release id: get_aws_lc_release
run: | 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
echo "result=$result" >> $GITHUB_OUTPUT echo "result=$result" >> $GITHUB_OUTPUT
- name: Cache AWS-LC - name: Cache AWS-LC
id: cache_aws_lc id: cache_aws_lc
uses: actions/cache@v5 uses: actions/cache@v4
with: with:
path: '~/opt/' path: '~/opt/'
key: ssl-${{ steps.get_aws_lc_release.outputs.result }}-Ubuntu-latest-gcc key: ssl-${{ steps.get_aws_lc_release.outputs.result }}-Ubuntu-latest-gcc
@ -54,12 +49,18 @@ jobs:
run: | run: |
ldd $(which haproxy) ldd $(which haproxy)
haproxy -vv haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT echo "version=$(haproxy -v |awk 'NR==1{print $3}')" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-vtest - name: Install problem matcher for VTest
run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest for HAProxy - name: Run VTest for HAProxy
id: vtest id: vtest
run: | 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
# allow to catch coredumps
ulimit -c unlimited
make reg-tests VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Run Unit tests - name: Run Unit tests
id: unittests id: unittests
run: | run: |
@ -97,3 +98,4 @@ jobs:
echo "::endgroup::" echo "::endgroup::"
done done
exit 1 exit 1

View file

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: codespell-project/codespell-problem-matcher@v1.2.0 - uses: codespell-project/codespell-problem-matcher@v1.2.0
- uses: codespell-project/actions-codespell@master - uses: codespell-project/actions-codespell@master
with: with:

View file

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install h2spec - name: Install h2spec
id: install-h2spec id: install-h2spec
run: | run: |
@ -45,7 +45,7 @@ jobs:
fi fi
echo "::endgroup::" echo "::endgroup::"
haproxy -vv 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 }} - name: Launch HAProxy ${{ steps.show-version.outputs.version }}
run: haproxy -f .github/h2spec.config -D run: haproxy -f .github/h2spec.config -D
- name: Run h2spec ${{ steps.install-h2spec.outputs.version }} - name: Run h2spec ${{ steps.install-h2spec.outputs.version }}

View file

@ -10,7 +10,10 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Compile admin/halog/halog
run: |
make admin/halog/halog
- name: Compile dev/flags/flags - name: Compile dev/flags/flags
run: | run: |
make dev/flags/flags make dev/flags/flags

View file

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install apt dependencies - name: Install apt dependencies
run: | run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
@ -27,7 +27,7 @@ jobs:
libsystemd-dev libsystemd-dev
- name: Install QUICTLS - name: Install QUICTLS
run: | run: |
QUICTLS_VERSION=OpenSSL_1_1_1w-quic1 scripts/build-ssl.sh QUICTLS=yes scripts/build-ssl.sh
- name: Download Coverity build tool - name: Download Coverity build tool
run: | run: |
wget -c -N https://scan.coverity.com/download/linux64 --post-data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=Haproxy" -O coverity_tool.tar.gz 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 +38,7 @@ jobs:
- name: Build with Coverity build tool - name: Build with Coverity build tool
run: | run: |
export PATH=`pwd`/coverity_tool/bin:$PATH 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_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=1 DEBUG+=-DDEBUG_USE_ABORT=1
- name: Submit build result to Coverity Scan - name: Submit build result to Coverity Scan
run: | run: |
tar czvf cov.tar.gz cov-int tar czvf cov.tar.gz cov-int

View file

@ -99,12 +99,12 @@ jobs:
sudo apt-get -yq --force-yes install \ sudo apt-get -yq --force-yes install \
gcc-${{ matrix.platform.arch }} \ gcc-${{ matrix.platform.arch }} \
${{ matrix.platform.libs }} ${{ matrix.platform.libs }}
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: install quictls - name: install quictls
run: | 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 - name: Build
run: | run: |

View file

@ -1,4 +1,4 @@
name: Fedora/Rawhide/OpenSSL name: Fedora/Rawhide/QuicTLS
on: on:
schedule: schedule:
@ -13,24 +13,26 @@ jobs:
strategy: strategy:
matrix: matrix:
platform: [ platform: [
{ name: x64, cc: gcc, ADDLIB_ATOMIC: "", ARCH_FLAGS: "" }, { name: x64, cc: gcc, QUICTLS_EXTRA_ARGS: "", ADDLIB_ATOMIC: "", ARCH_FLAGS: "" },
{ name: x64, cc: clang, ADDLIB_ATOMIC: "", ARCH_FLAGS: "" }, { name: x64, cc: clang, QUICTLS_EXTRA_ARGS: "", ADDLIB_ATOMIC: "", ARCH_FLAGS: "" },
{ name: x86, cc: gcc, ADDLIB_ATOMIC: "-latomic", ARCH_FLAGS: "-m32" }, { name: x86, cc: gcc, QUICTLS_EXTRA_ARGS: "-m32 linux-generic32", ADDLIB_ATOMIC: "-latomic", ARCH_FLAGS: "-m32" },
{ name: x86, cc: clang, ADDLIB_ATOMIC: "-latomic", ARCH_FLAGS: "-m32" } { name: x86, cc: clang, QUICTLS_EXTRA_ARGS: "-m32 linux-generic32", ADDLIB_ATOMIC: "-latomic", ARCH_FLAGS: "-m32" }
] ]
fail-fast: false
name: ${{ matrix.platform.cc }}.${{ matrix.platform.name }} name: ${{ matrix.platform.cc }}.${{ matrix.platform.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
container: container:
image: fedora:rawhide image: fedora:rawhide
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install dependencies - name: Install dependencies
run: | 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 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
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 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
- uses: ./.github/actions/setup-vtest - 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 - name: Build contrib tools
run: | run: |
make admin/halog/halog make admin/halog/halog
@ -39,7 +41,7 @@ jobs:
make dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht make dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht
- name: Compile HAProxy with ${{ matrix.platform.cc }} - name: Compile HAProxy with ${{ matrix.platform.cc }}
run: | 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 DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" USE_OPENSSL=1 USE_QUIC=1 USE_ZLIB=1 USE_PCRE=1 USE_PCRE_JIT=1 USE_LUA=1 ADDLIB="${{ matrix.platform.ADDLIB_ATOMIC }} -Wl,-rpath,${HOME}/opt/lib" SSL_LIB=${HOME}/opt/lib SSL_INC=${HOME}/opt/include ARCH_FLAGS="${{ matrix.platform.ARCH_FLAGS }}"
make install make install
- name: Show HAProxy version - name: Show HAProxy version
id: show-version id: show-version
@ -48,18 +50,11 @@ jobs:
ldd $(command -v haproxy) ldd $(command -v haproxy)
echo "::endgroup::" echo "::endgroup::"
haproxy -vv haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT echo "version=$(haproxy -v |awk 'NR==1{print $3}')" >> $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
- name: Run VTest for HAProxy ${{ steps.show-version.outputs.version }} - name: Run VTest for HAProxy ${{ steps.show-version.outputs.version }}
id: vtest id: vtest
run: | 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 - name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }} if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: | run: |
@ -72,4 +67,4 @@ jobs:
- name: Run Unit tests - name: Run Unit tests
id: unittests id: unittests
run: | run: |
make unit-tests make unit-tests

View file

@ -5,16 +5,15 @@ on:
- cron: "0 0 25 * *" - cron: "0 0 25 * *"
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
jobs: jobs:
gcc: gcc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
permissions:
contents: read
steps: steps:
- name: "Checkout repository" - name: "Checkout repository"
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: "Build on VM" - name: "Build on VM"
uses: vmactions/solaris-vm@v1 uses: vmactions/solaris-vm@v1

View file

@ -20,10 +20,11 @@ jobs:
run: | run: |
ulimit -c unlimited ulimit -c unlimited
echo '/tmp/core/core.%h.%e.%t' > /proc/sys/kernel/core_pattern echo '/tmp/core/core.%h.%e.%t' > /proc/sys/kernel/core_pattern
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install dependencies - 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 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: Install VTest
run: scripts/build-vtest.sh
- name: Build - name: Build
run: make -j$(nproc) TARGET=linux-musl DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" ARCH_FLAGS='-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 run: make -j$(nproc) TARGET=linux-musl DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" ARCH_FLAGS='-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 - name: Show version
@ -35,7 +36,7 @@ jobs:
run: echo "::add-matcher::.github/vtest.json" run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest - name: Run VTest
id: vtest id: vtest
run: make reg-tests VTEST_PROGRAM=${{ github.workspace }}/vtest/vtest REGTESTS_TYPES=default,bug,devel run: make reg-tests VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Run Unit tests - name: Run Unit tests
id: unittests id: unittests
run: | run: |

View file

@ -5,16 +5,15 @@ on:
- cron: "0 0 25 * *" - cron: "0 0 25 * *"
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
jobs: jobs:
gcc: gcc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
permissions:
contents: read
steps: steps:
- name: "Checkout repository" - name: "Checkout repository"
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: "Build on VM" - name: "Build on VM"
uses: vmactions/netbsd-vm@v1 uses: vmactions/netbsd-vm@v1

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

@ -0,0 +1,34 @@
#
# 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"
workflow_dispatch:
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

@ -9,58 +9,96 @@ on:
schedule: schedule:
- cron: "0 0 * * 2" - cron: "0 0 * * 2"
permissions:
contents: read
jobs: jobs:
combined-build-and-run: build:
runs-on: ubuntu-24.04
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v5
with:
context: https://github.com/haproxytech/haproxy-qns.git
push: true
build-args: |
SSLLIB: AWS-LC
tags: ghcr.io/${{ github.repository }}:aws-lc
- name: Cleanup registry
uses: actions/delete-package-versions@v5
with:
owner: ${{ github.repository_owner }}
package-name: 'haproxy'
package-type: container
min-versions-to-keep: 1
delete-only-untagged-versions: 'true'
run:
needs: build
strategy:
matrix:
suite: [
{ client: chrome, tests: "http3" },
{ client: picoquic, tests: "handshake,transfer,longrtt,chacha20,multiplexing,retry,resumption,zerortt,http3,blackhole,keyupdate,ecn,amplificationlimit,handshakeloss,transferloss,handshakecorruption,transfercorruption,ipv6,v2" },
{ client: quic-go, tests: "handshake,transfer,longrtt,chacha20,multiplexing,retry,resumption,zerortt,http3,blackhole,keyupdate,ecn,amplificationlimit,handshakeloss,transferloss,handshakecorruption,transfercorruption,ipv6,v2" },
{ client: ngtcp2, tests: "handshake,transfer,longrtt,chacha20,multiplexing,retry,resumption,zerortt,http3,blackhole,keyupdate,ecn,amplificationlimit,handshakeloss,transferloss,handshakecorruption,transfercorruption,ipv6,v2" }
]
fail-fast: false
name: ${{ matrix.suite.client }}
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Update Docker to the latest - name: Log in to the Container registry
uses: docker/setup-docker-action@v4 uses: docker/login-action@v3
- name: Build Docker image
id: push
uses: docker/build-push-action@v6
with: with:
context: https://github.com/haproxytech/haproxy-qns.git registry: ghcr.io
platforms: linux/amd64 username: ${{ github.actor }}
build-args: | password: ${{ secrets.GITHUB_TOKEN }}
SSLLIB=AWS-LC
tags: local:aws-lc
- name: Install tshark - name: Install tshark
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install tshark sudo apt-get -y install tshark
- name: Pull image
run: |
docker pull ghcr.io/${{ github.repository }}:aws-lc
- name: Run - name: Run
run: | run: |
git clone https://github.com/quic-interop/quic-interop-runner git clone https://github.com/quic-interop/quic-interop-runner
cd quic-interop-runner cd quic-interop-runner
pip install -r requirements.txt --break-system-packages 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 -r haproxy=ghcr.io/${{ github.repository }}:aws-lc -t ${{ matrix.suite.tests }} -c ${{ matrix.suite.client }} -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 - name: Delete succeeded logs
if: ${{ failure() }} if: failure()
run: | run: |
for client in chrome picoquic quic-go ngtcp2; do cd quic-interop-runner/logs/haproxy_${{ matrix.suite.client }}
pushd quic-interop-runner/logs-${client}/haproxy_${client} cat ../../result.json | jq -r '.results[][] | select(.result=="succeeded") | .name' | xargs rm -rf
cat ../../result.json | jq -r '.results[][] | select(.result=="succeeded") | .name' | xargs rm -rf
popd
done
- name: Logs upload - name: Logs upload
if: ${{ failure() }} if: failure()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: logs name: logs-${{ matrix.suite.client }}
path: quic-interop-runner/logs*/ path: quic-interop-runner/logs/
retention-days: 6 retention-days: 6

View file

@ -9,56 +9,94 @@ on:
schedule: schedule:
- cron: "0 0 * * 2" - cron: "0 0 * * 2"
permissions:
contents: read
jobs: jobs:
combined-build-and-run: build:
runs-on: ubuntu-24.04
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v5
with:
context: https://github.com/haproxytech/haproxy-qns.git
push: true
build-args: |
SSLLIB: LibreSSL
tags: ghcr.io/${{ github.repository }}:libressl
- name: Cleanup registry
uses: actions/delete-package-versions@v5
with:
owner: ${{ github.repository_owner }}
package-name: 'haproxy'
package-type: container
min-versions-to-keep: 1
delete-only-untagged-versions: 'true'
run:
needs: build
strategy:
matrix:
suite: [
{ client: picoquic, tests: "handshake,transfer,longrtt,chacha20,multiplexing,retry,http3,blackhole,amplificationlimit,handshakeloss,transferloss,handshakecorruption,transfercorruption,v2" },
{ client: quic-go, tests: "handshake,transfer,longrtt,chacha20,multiplexing,retry,http3,blackhole,amplificationlimit,transferloss,transfercorruption,v2" }
]
fail-fast: false
name: ${{ matrix.suite.client }}
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Update Docker to the latest - name: Log in to the Container registry
uses: docker/setup-docker-action@v4 uses: docker/login-action@v3
- name: Build Docker image
id: push
uses: docker/build-push-action@v6
with: with:
context: https://github.com/haproxytech/haproxy-qns.git registry: ghcr.io
platforms: linux/amd64 username: ${{ github.actor }}
build-args: | password: ${{ secrets.GITHUB_TOKEN }}
SSLLIB=LibreSSL
tags: local:libressl
- name: Install tshark - name: Install tshark
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install tshark sudo apt-get -y install tshark
- name: Pull image
run: |
docker pull ghcr.io/${{ github.repository }}:libressl
- name: Run - name: Run
run: | run: |
git clone https://github.com/quic-interop/quic-interop-runner git clone https://github.com/quic-interop/quic-interop-runner
cd quic-interop-runner cd quic-interop-runner
pip install -r requirements.txt --break-system-packages 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 -r haproxy=ghcr.io/${{ github.repository }}:libressl -t ${{ matrix.suite.tests }} -c ${{ matrix.suite.client }} -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 - name: Delete succeeded logs
if: ${{ failure() }} if: failure()
run: | run: |
for client in picoquic quic-go; do cd quic-interop-runner/logs/haproxy_${{ matrix.suite.client }}
pushd quic-interop-runner/logs-${client}/haproxy_${client} cat ../../result.json | jq -r '.results[][] | select(.result=="succeeded") | .name' | xargs rm -rf
cat ../../result.json | jq -r '.results[][] | select(.result=="succeeded") | .name' | xargs rm -rf
popd
done
- name: Logs upload - name: Logs upload
if: ${{ failure() }} if: failure()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: logs name: logs-${{ matrix.suite.client }}
path: quic-interop-runner/logs*/ path: quic-interop-runner/logs/
retention-days: 6 retention-days: 6

View file

@ -17,13 +17,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install VTest
run: |
scripts/build-vtest.sh
- name: Install apt dependencies - name: Install apt dependencies
run: | run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none 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 socat gdb
- name: Install QuicTLS - name: Install QuicTLS
run: env QUICTLS_VERSION=main QUICTLS_URL=https://github.com/quictls/quictls scripts/build-ssl.sh run: env QUICTLS=yes QUICTLS_URL=https://github.com/quictls/quictls scripts/build-ssl.sh
- name: Compile HAProxy - name: Compile HAProxy
run: | run: |
make -j$(nproc) ERR=1 CC=gcc TARGET=linux-glibc \ make -j$(nproc) ERR=1 CC=gcc TARGET=linux-glibc \
@ -38,12 +41,18 @@ jobs:
run: | run: |
ldd $(which haproxy) ldd $(which haproxy)
haproxy -vv haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT echo "version=$(haproxy -v |awk 'NR==1{print $3}')" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-vtest - name: Install problem matcher for VTest
run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest for HAProxy - name: Run VTest for HAProxy
id: vtest id: vtest
run: | 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
# allow to catch coredumps
ulimit -c unlimited
make reg-tests VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show VTest results - name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }} if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: | run: |

View file

@ -23,7 +23,7 @@ jobs:
outputs: outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }} matrix: ${{ steps.set-matrix.outputs.matrix }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Generate Build Matrix - name: Generate Build Matrix
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -44,10 +44,16 @@ jobs:
TMPDIR: /tmp TMPDIR: /tmp
OT_CPP_VERSION: 1.6.0 OT_CPP_VERSION: 1.6.0
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
fetch-depth: 100 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 # Github Action cache key cannot contain comma, so we calculate it based on job name
# #
@ -57,9 +63,9 @@ jobs:
echo "key=$(echo ${{ matrix.name }} | sha256sum | awk '{print $1}')" >> $GITHUB_OUTPUT echo "key=$(echo ${{ matrix.name }} | sha256sum | awk '{print $1}')" >> $GITHUB_OUTPUT
- name: Cache SSL libs - 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 id: cache_ssl
uses: actions/cache@v5 uses: actions/cache@v4
with: with:
path: '~/opt/' path: '~/opt/'
key: ssl-${{ steps.generate-cache-key.outputs.key }} key: ssl-${{ steps.generate-cache-key.outputs.key }}
@ -67,10 +73,10 @@ jobs:
- name: Cache OpenTracing - name: Cache OpenTracing
if: ${{ contains(matrix.FLAGS, 'USE_OT=1') }} if: ${{ contains(matrix.FLAGS, 'USE_OT=1') }}
id: cache_ot id: cache_ot
uses: actions/cache@v5 uses: actions/cache@v4
with: with:
path: '~/opt-ot/' 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 - name: Install apt dependencies
if: ${{ startsWith(matrix.os, 'ubuntu-') }} if: ${{ startsWith(matrix.os, 'ubuntu-') }}
run: | run: |
@ -87,7 +93,9 @@ jobs:
run: | run: |
brew install socat brew install socat
brew install lua brew install lua
- uses: ./.github/actions/setup-vtest - name: Install VTest
run: |
scripts/build-vtest.sh
- name: Install SSL ${{ matrix.ssl }} - name: Install SSL ${{ matrix.ssl }}
if: ${{ matrix.ssl && matrix.ssl != 'stock' && steps.cache_ssl.outputs.cache-hit != 'true' }} if: ${{ matrix.ssl && matrix.ssl != 'stock' && steps.cache_ssl.outputs.cache-hit != 'true' }}
run: env ${{ matrix.ssl }} scripts/build-ssl.sh run: env ${{ matrix.ssl }} scripts/build-ssl.sh
@ -113,16 +121,7 @@ jobs:
DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \ DEBUG="-DDEBUG_POOL_INTEGRITY -DDEBUG_UNIT" \
${{ join(matrix.FLAGS, ' ') }} \ ${{ join(matrix.FLAGS, ' ') }} \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/" ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/"
sudo make install-bin sudo make install
- 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" \
${{ join(matrix.FLAGS, ' ') }} \
ADDLIB="-Wl,-rpath,/usr/local/lib/ -Wl,-rpath,$HOME/opt/lib/"
- name: Show HAProxy version - name: Show HAProxy version
id: show-version id: show-version
run: | run: |
@ -136,11 +135,18 @@ jobs:
fi fi
echo "::endgroup::" echo "::endgroup::"
haproxy -vv 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 }} - name: Run VTest for HAProxy ${{ steps.show-version.outputs.version }}
id: vtest id: vtest
run: | 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 VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Show VTest results - name: Show VTest results
if: ${{ failure() && steps.vtest.outcome == 'failure' }} if: ${{ failure() && steps.vtest.outcome == 'failure' }}
run: | run: |

View file

@ -18,7 +18,6 @@ jobs:
msys2: msys2:
name: ${{ matrix.name }} name: ${{ matrix.name }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
defaults: defaults:
run: run:
shell: msys2 {0} shell: msys2 {0}
@ -36,7 +35,7 @@ jobs:
- USE_THREAD=1 - USE_THREAD=1
- USE_ZLIB=1 - USE_ZLIB=1
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
with: with:
install: >- install: >-

View file

@ -13,7 +13,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }} if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install VTest
run: |
scripts/build-vtest.sh
- name: Install apt dependencies - name: Install apt dependencies
run: | run: |
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
@ -34,12 +37,18 @@ jobs:
run: | run: |
ldd $(which haproxy) ldd $(which haproxy)
haproxy -vv haproxy -vv
echo "version=$(haproxy -vq)" >> $GITHUB_OUTPUT echo "version=$(haproxy -v |awk 'NR==1{print $3}')" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-vtest - name: Install problem matcher for VTest
run: echo "::add-matcher::.github/vtest.json"
- name: Run VTest for HAProxy - name: Run VTest for HAProxy
id: vtest id: vtest
run: | 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
# allow to catch coredumps
ulimit -c unlimited
make reg-tests VTEST_PROGRAM=../vtest/vtest REGTESTS_TYPES=default,bug,devel
- name: Run Unit tests - name: Run Unit tests
id: unittests id: unittests
run: | run: |

View file

@ -171,17 +171,7 @@ feedback for developers:
as the previous releases that had 6 months to stabilize. In terms of as the previous releases that had 6 months to stabilize. In terms of
stability it really means that the point zero version already accumulated 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 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" released.
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.
- for developers, given that the odd versions are solely used by highly - 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 skilled users, it's easier to get advanced traces and captures, and there

2064
CHANGELOG

File diff suppressed because it is too large Load diff

45
INSTALL
View file

@ -111,7 +111,7 @@ 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 may want to retry with "gmake" which is the name commonly used for GNU make
on BSD systems. on BSD systems.
- GCC >= 4.7 (up to 15 tested). Older versions are no longer supported due to - GCC >= 4.7 (up to 14 tested). Older versions are no longer supported due to
the latest mt_list update which only uses c11-like atomics. Newer versions the latest mt_list update which only uses c11-like atomics. Newer versions
may sometimes break due to compiler regressions or behaviour changes. The may sometimes break due to compiler regressions or behaviour changes. The
version shipped with your operating system is very likely to work with no version shipped with your operating system is very likely to work with no
@ -237,7 +237,7 @@ to forcefully enable it using "USE_LIBCRYPT=1".
----------------- -----------------
For SSL/TLS, it is necessary to use a cryptography library. HAProxy currently 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 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, and 3.0 to 3.4. It is recommended to use
at least OpenSSL 1.1.1 to have support for all SSL keywords and configuration 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, 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 and each of the branches above receives its own fixes, without forcing you to
@ -259,15 +259,11 @@ 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 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 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 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 OpenSSL. In this case, QUICTLS is the preferred alternative. As of writing
alternatives. As of writing this, the QuicTLS project follows OpenSSL very this, the QuicTLS project follows OpenSSL very closely and provides update
closely and provides update simultaneously, but being a volunteer-driven simultaneously, but being a volunteer-driven project, its long-term future does
project, its long-term future does not look certain enough to convince not look certain enough to convince operating systems to package it, so it
operating systems to package it, so it needs to be build locally. Recent needs to be build locally. See the section about QUIC in this document.
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.
A fifth option is wolfSSL (https://github.com/wolfSSL/wolfssl). It is the only 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 supported alternative stack not based on OpenSSL, yet which implements almost
@ -504,11 +500,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 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". 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 Note that QUIC is not fully supported by the OpenSSL library. Indeed QUIC 0-RTT
its version. Indeed QUIC 0-RTT cannot be supported by OpenSSL for versions before cannot be supported by OpenSSL contrary to others libraries with full QUIC
3.5 contrary to others libraries with full QUIC support. The preferred option is support. The preferred option is to use QUICTLS. This is a fork of OpenSSL with
to use QUICTLS. This is a fork of OpenSSL with a QUIC-compatible API. Its a QUIC-compatible API. Its repository is available at this location:
repository is available at this location:
https://github.com/quictls/openssl https://github.com/quictls/openssl
@ -536,18 +531,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 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" LDFLAGS="-Wl,-rpath,/opt/wolfssl-5.6.0/lib"
As last resort, haproxy may be compiled against OpenSSL as follows from 3.5 As last resort, haproxy may be compiled against OpenSSL as follows:
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:
$ make TARGET=generic USE_OPENSSL=1 USE_QUIC=1 USE_QUIC_OPENSSL_COMPAT=1 $ 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 Note that QUIC 0-RTT is not supported by haproxy QUIC stack when built against
enabled with a specific QUIC tuning parameter. (see "limited-quic" global OpenSSL. In addition to this compilation requirements, the QUIC listener
parameter of haproxy Configuration Manual). 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 5) How to build HAProxy
@ -563,9 +554,9 @@ It goes into more details with the main options.
To build haproxy, you have to choose your target OS amongst the following ones To build haproxy, you have to choose your target OS amongst the following ones
and assign it to the TARGET variable : 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-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 - solaris for Solaris 10 and above
- freebsd for FreeBSD 10 and above - freebsd for FreeBSD 10 and above
- dragonfly for DragonFlyBSD 4.3 and above - dragonfly for DragonFlyBSD 4.3 and above

View file

@ -35,7 +35,6 @@
# USE_OPENSSL : enable use of OpenSSL. Recommended, but see below. # USE_OPENSSL : enable use of OpenSSL. Recommended, but see below.
# USE_OPENSSL_AWSLC : enable use of AWS-LC # USE_OPENSSL_AWSLC : enable use of AWS-LC
# USE_OPENSSL_WOLFSSL : enable use of wolfSSL with the OpenSSL API # USE_OPENSSL_WOLFSSL : enable use of wolfSSL with the OpenSSL API
# USE_ECH : enable use of ECH with the OpenSSL API
# USE_QUIC : enable use of QUIC with the quictls API (quictls, libressl, boringssl) # USE_QUIC : enable use of QUIC with the quictls API (quictls, libressl, boringssl)
# USE_QUIC_OPENSSL_COMPAT : enable use of QUIC with the standard openssl API (limited features) # USE_QUIC_OPENSSL_COMPAT : enable use of QUIC with the standard openssl API (limited features)
# USE_ENGINE : enable use of OpenSSL Engine. # USE_ENGINE : enable use of OpenSSL Engine.
@ -60,12 +59,9 @@
# USE_OBSOLETE_LINKER : use when the linker fails to emit __start_init/__stop_init # USE_OBSOLETE_LINKER : use when the linker fails to emit __start_init/__stop_init
# USE_THREAD_DUMP : use the more advanced thread state dump system. Automatic. # USE_THREAD_DUMP : use the more advanced thread state dump system. Automatic.
# USE_OT : enable the OpenTracing filter # USE_OT : enable the OpenTracing filter
# USE_OTEL : enable the OpenTelemetry filter
# USE_MEMORY_PROFILING : enable the memory profiler. Linux-glibc only. # USE_MEMORY_PROFILING : enable the memory profiler. Linux-glibc only.
# USE_LIBATOMIC : force to link with/without libatomic. Automatic. # USE_LIBATOMIC : force to link with/without libatomic. Automatic.
# USE_PTHREAD_EMULATION : replace pthread's rwlocks with ours # USE_PTHREAD_EMULATION : replace pthread's rwlocks with ours
# USE_SHM_OPEN : use shm_open() for features that can make use of shared memory
# USE_KTLS : use kTLS.(requires at least Linux 4.17).
# #
# Options can be forced by specifying "USE_xxx=1" or can be disabled by using # Options can be forced by specifying "USE_xxx=1" or can be disabled by using
# "USE_xxx=" (empty string). The list of enabled and disabled options for a # "USE_xxx=" (empty string). The list of enabled and disabled options for a
@ -129,11 +125,6 @@
# OT_LIB : force the lib path to libopentracing-c-wrapper # OT_LIB : force the lib path to libopentracing-c-wrapper
# OT_RUNPATH : add RUNPATH for libopentracing-c-wrapper to haproxy executable # OT_RUNPATH : add RUNPATH for libopentracing-c-wrapper to haproxy executable
# OT_USE_VARS : allows the use of variables for the OpenTracing context # OT_USE_VARS : allows the use of variables for the OpenTracing context
# 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 RUNPATH for libopentelemetry-c-wrapper to haproxy executable
# OTEL_USE_VARS : allows the use of variables for the OpenTelemetry context
# IGNOREGIT : ignore GIT commit versions if set. # IGNOREGIT : ignore GIT commit versions if set.
# VERSION : force haproxy version reporting. # VERSION : force haproxy version reporting.
# SUBVERS : add a sub-version (eg: platform, model, ...). # SUBVERS : add a sub-version (eg: platform, model, ...).
@ -220,8 +211,7 @@ UNIT_TEST_SCRIPT=./scripts/run-unittests.sh
# undefined behavior to silently produce invalid code. For this reason we have # undefined behavior to silently produce invalid code. For this reason we have
# to use -fwrapv or -fno-strict-overflow to guarantee the intended behavior. # to use -fwrapv or -fno-strict-overflow to guarantee the intended behavior.
# It is preferable not to change this option in order to avoid breakage. # It is preferable not to change this option in order to avoid breakage.
STD_CFLAGS := $(call cc-opt-alt,-fwrapv,-fno-strict-overflow) \ STD_CFLAGS := $(call cc-opt-alt,-fwrapv,-fno-strict-overflow)
$(call cc-opt,-fvect-cost-model=very-cheap)
#### Compiler-specific flags to enable certain classes of warnings. #### Compiler-specific flags to enable certain classes of warnings.
# Some are hard-coded, others are enabled only if supported. # Some are hard-coded, others are enabled only if supported.
@ -348,16 +338,14 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER USE_POLL \
USE_TPROXY USE_LINUX_TPROXY USE_LINUX_CAP \ USE_TPROXY USE_LINUX_TPROXY USE_LINUX_CAP \
USE_LINUX_SPLICE USE_LIBCRYPT USE_CRYPT_H USE_ENGINE \ USE_LINUX_SPLICE USE_LIBCRYPT USE_CRYPT_H USE_ENGINE \
USE_GETADDRINFO USE_OPENSSL USE_OPENSSL_WOLFSSL USE_OPENSSL_AWSLC \ USE_GETADDRINFO USE_OPENSSL USE_OPENSSL_WOLFSSL USE_OPENSSL_AWSLC \
USE_ECH \
USE_SSL USE_LUA USE_ACCEPT4 USE_CLOSEFROM USE_ZLIB USE_SLZ \ USE_SSL USE_LUA USE_ACCEPT4 USE_CLOSEFROM USE_ZLIB USE_SLZ \
USE_CPU_AFFINITY USE_TFO USE_NS USE_DL USE_RT USE_LIBATOMIC \ USE_CPU_AFFINITY USE_TFO USE_NS USE_DL USE_RT USE_LIBATOMIC \
USE_MATH USE_DEVICEATLAS USE_51DEGREES \ USE_MATH USE_DEVICEATLAS USE_51DEGREES \
USE_WURFL USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL \ USE_WURFL USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL \
USE_THREAD_DUMP USE_EVPORTS USE_OT USE_OTEL USE_QUIC USE_PROMEX \ USE_THREAD_DUMP USE_EVPORTS USE_OT USE_QUIC USE_PROMEX \
USE_MEMORY_PROFILING USE_SHM_OPEN \ USE_MEMORY_PROFILING \
USE_STATIC_PCRE USE_STATIC_PCRE2 \ USE_STATIC_PCRE USE_STATIC_PCRE2 \
USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT \ USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT USE_QUIC_OPENSSL_COMPAT
USE_QUIC_OPENSSL_COMPAT USE_KTLS
# preset all variables for all supported build options among use_opts # preset all variables for all supported build options among use_opts
$(reset_opts_vars) $(reset_opts_vars)
@ -388,13 +376,13 @@ ifeq ($(TARGET),haiku)
set_target_defaults = $(call default_opts,USE_POLL USE_TPROXY USE_OBSOLETE_LINKER) set_target_defaults = $(call default_opts,USE_POLL USE_TPROXY USE_OBSOLETE_LINKER)
endif endif
# For linux >= 4.17 and glibc # For linux >= 2.6.28 and glibc
ifeq ($(TARGET),linux-glibc) ifeq ($(TARGET),linux-glibc)
set_target_defaults = $(call default_opts, \ set_target_defaults = $(call default_opts, \
USE_POLL USE_TPROXY USE_LIBCRYPT USE_DL USE_RT USE_CRYPT_H USE_NETFILTER \ USE_POLL USE_TPROXY USE_LIBCRYPT USE_DL USE_RT USE_CRYPT_H USE_NETFILTER \
USE_CPU_AFFINITY USE_THREAD USE_EPOLL USE_LINUX_TPROXY USE_LINUX_CAP \ USE_CPU_AFFINITY USE_THREAD USE_EPOLL USE_LINUX_TPROXY USE_LINUX_CAP \
USE_ACCEPT4 USE_LINUX_SPLICE USE_PRCTL USE_THREAD_DUMP USE_NS USE_TFO \ USE_ACCEPT4 USE_LINUX_SPLICE USE_PRCTL USE_THREAD_DUMP USE_NS USE_TFO \
USE_GETADDRINFO USE_BACKTRACE USE_SHM_OPEN USE_KTLS) USE_GETADDRINFO USE_BACKTRACE)
INSTALL = install -v INSTALL = install -v
endif endif
@ -407,13 +395,13 @@ ifeq ($(TARGET),linux-glibc-legacy)
INSTALL = install -v INSTALL = install -v
endif endif
# For linux >= 4.17 and musl # For linux >= 2.6.28 and musl
ifeq ($(TARGET),linux-musl) ifeq ($(TARGET),linux-musl)
set_target_defaults = $(call default_opts, \ set_target_defaults = $(call default_opts, \
USE_POLL USE_TPROXY USE_LIBCRYPT USE_DL USE_RT USE_CRYPT_H USE_NETFILTER \ USE_POLL USE_TPROXY USE_LIBCRYPT USE_DL USE_RT USE_CRYPT_H USE_NETFILTER \
USE_CPU_AFFINITY USE_THREAD USE_EPOLL USE_LINUX_TPROXY USE_LINUX_CAP \ USE_CPU_AFFINITY USE_THREAD USE_EPOLL USE_LINUX_TPROXY USE_LINUX_CAP \
USE_ACCEPT4 USE_LINUX_SPLICE USE_PRCTL USE_THREAD_DUMP USE_NS USE_TFO \ USE_ACCEPT4 USE_LINUX_SPLICE USE_PRCTL USE_THREAD_DUMP USE_NS USE_TFO \
USE_GETADDRINFO USE_BACKTRACE USE_SHM_OPEN USE_KTLS) USE_GETADDRINFO USE_BACKTRACE)
INSTALL = install -v INSTALL = install -v
endif endif
@ -607,10 +595,6 @@ ifneq ($(USE_BACKTRACE:0=),)
BACKTRACE_CFLAGS = -fno-omit-frame-pointer BACKTRACE_CFLAGS = -fno-omit-frame-pointer
endif endif
ifneq ($(USE_MEMORY_PROFILING:0=),)
MEMORY_PROFILING_CFLAGS = -fno-optimize-sibling-calls
endif
ifneq ($(USE_CPU_AFFINITY:0=),) ifneq ($(USE_CPU_AFFINITY:0=),)
OPTIONS_OBJS += src/cpuset.o OPTIONS_OBJS += src/cpuset.o
OPTIONS_OBJS += src/cpu_topo.o OPTIONS_OBJS += src/cpu_topo.o
@ -649,7 +633,7 @@ ifneq ($(USE_OPENSSL:0=),)
OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o \ OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o \
src/ssl_sample.o src/cfgparse-ssl.o src/ssl_gencert.o \ src/ssl_sample.o src/cfgparse-ssl.o src/ssl_gencert.o \
src/ssl_utils.o src/jwt.o src/ssl_clienthello.o src/jws.o src/acme.o \ src/ssl_utils.o src/jwt.o src/ssl_clienthello.o src/jws.o src/acme.o \
src/acme_resolvers.o src/ssl_trace.o src/jwe.o src/ssl_trace.o
endif endif
ifneq ($(USE_ENGINE:0=),) ifneq ($(USE_ENGINE:0=),)
@ -676,7 +660,7 @@ OPTIONS_OBJS += src/mux_quic.o src/h3.o src/quic_rx.o src/quic_tx.o \
src/quic_cc_nocc.o src/quic_cc.o src/quic_pacing.o \ src/quic_cc_nocc.o src/quic_cc.o src/quic_pacing.o \
src/h3_stats.o src/quic_stats.o src/qpack-enc.o \ src/h3_stats.o src/quic_stats.o src/qpack-enc.o \
src/qpack-tbl.o src/quic_cc_drs.o src/quic_fctl.o \ src/qpack-tbl.o src/quic_cc_drs.o src/quic_fctl.o \
src/quic_enc.o src/mux_quic_qstrm.o src/xprt_qstrm.o src/cbuf.o src/quic_enc.o
endif endif
ifneq ($(USE_QUIC_OPENSSL_COMPAT:0=),) ifneq ($(USE_QUIC_OPENSSL_COMPAT:0=),)
@ -868,10 +852,6 @@ ifneq ($(USE_OT:0=),)
include addons/ot/Makefile include addons/ot/Makefile
endif endif
ifneq ($(USE_OTEL:0=),)
include addons/otel/Makefile
endif
# better keep this one close to the end, as several libs above may need it # better keep this one close to the end, as several libs above may need it
ifneq ($(USE_DL:0=),) ifneq ($(USE_DL:0=),)
DL_LDFLAGS = -ldl DL_LDFLAGS = -ldl
@ -966,7 +946,6 @@ endif # obsolete targets
endif # TARGET endif # TARGET
OBJS = OBJS =
HATERM_OBJS =
ifneq ($(EXTRA_OBJS),) ifneq ($(EXTRA_OBJS),)
OBJS += $(EXTRA_OBJS) OBJS += $(EXTRA_OBJS)
@ -980,15 +959,15 @@ OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.o \
src/cache.o src/stconn.o src/http_htx.o src/debug.o \ src/cache.o src/stconn.o src/http_htx.o src/debug.o \
src/check.o src/stats-html.o src/haproxy.o src/listener.o \ src/check.o src/stats-html.o src/haproxy.o src/listener.o \
src/applet.o src/pattern.o src/cfgparse-listen.o \ src/applet.o src/pattern.o src/cfgparse-listen.o \
src/flt_spoe.o src/cebis_tree.o src/http_ext.o \ src/flt_spoe.o src/cebuis_tree.o src/http_ext.o \
src/http_act.o src/http_fetch.o src/cebs_tree.o \ src/http_act.o src/http_fetch.o src/cebus_tree.o \
src/cebib_tree.o src/http_client.o src/dns.o \ src/cebuib_tree.o src/http_client.o src/dns.o \
src/cebb_tree.o src/vars.o src/event_hdl.o src/tcp_rules.o \ src/cebub_tree.o src/vars.o src/event_hdl.o src/tcp_rules.o \
src/trace.o src/stats-proxy.o src/pool.o src/stats.o \ src/trace.o src/stats-proxy.o src/pool.o src/stats.o \
src/cfgparse-global.o src/filters.o src/mux_pt.o \ src/cfgparse-global.o src/filters.o src/mux_pt.o \
src/flt_http_comp.o src/sock.o src/h1.o src/sink.o \ src/flt_http_comp.o src/sock.o src/h1.o src/sink.o \
src/ceba_tree.o src/session.o src/payload.o src/htx.o \ src/cebua_tree.o src/session.o src/payload.o src/htx.o \
src/cebl_tree.o src/ceb32_tree.o src/ceb64_tree.o \ src/cebul_tree.o src/cebu32_tree.o src/cebu64_tree.o \
src/server_state.o src/proto_rhttp.o src/flt_trace.o src/fd.o \ src/server_state.o src/proto_rhttp.o src/flt_trace.o src/fd.o \
src/task.o src/map.o src/fcgi-app.o src/h2.o src/mworker.o \ src/task.o src/map.o src/fcgi-app.o src/h2.o src/mworker.o \
src/tcp_sample.o src/mjson.o src/h1_htx.o src/tcp_act.o \ src/tcp_sample.o src/mjson.o src/h1_htx.o src/tcp_act.o \
@ -1003,9 +982,9 @@ OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.o \
src/cfgcond.o src/proto_udp.o src/lb_fwlc.o src/ebmbtree.o \ src/cfgcond.o src/proto_udp.o src/lb_fwlc.o src/ebmbtree.o \
src/proto_uxdg.o src/cfgdiag.o src/sock_unix.o src/sha1.o \ src/proto_uxdg.o src/cfgdiag.o src/sock_unix.o src/sha1.o \
src/lb_fas.o src/clock.o src/sock_inet.o src/ev_select.o \ src/lb_fas.o src/clock.o src/sock_inet.o src/ev_select.o \
src/lb_map.o src/shctx.o src/hpack-dec.o src/net_helper.o \ src/lb_map.o src/shctx.o src/mworker-prog.o src/hpack-dec.o \
src/arg.o src/signal.o src/fix.o src/dynbuf.o src/guid.o \ src/arg.o src/signal.o src/fix.o src/dynbuf.o src/guid.o \
src/cfgparse-tcp.o src/lb_ss.o src/chunk.o src/counters.o \ src/cfgparse-tcp.o src/lb_ss.o src/chunk.o \
src/cfgparse-unix.o src/regex.o src/fcgi.o src/uri_auth.o \ src/cfgparse-unix.o src/regex.o src/fcgi.o src/uri_auth.o \
src/eb64tree.o src/eb32tree.o src/eb32sctree.o src/lru.o \ src/eb64tree.o src/eb32tree.o src/eb32sctree.o src/lru.o \
src/limits.o src/ebimtree.o src/wdt.o src/hpack-tbl.o \ src/limits.o src/ebimtree.o src/wdt.o src/hpack-tbl.o \
@ -1013,15 +992,12 @@ OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.o \
src/ebsttree.o src/freq_ctr.o src/systemd.o src/init.o \ src/ebsttree.o src/freq_ctr.o src/systemd.o src/init.o \
src/http_acl.o src/dict.o src/dgram.o src/pipe.o \ src/http_acl.o src/dict.o src/dgram.o src/pipe.o \
src/hpack-huff.o src/hpack-enc.o src/ebtree.o src/hash.o \ src/hpack-huff.o src/hpack-enc.o src/ebtree.o src/hash.o \
src/httpclient_cli.o src/version.o src/ncbmbuf.o src/ech.o \ src/version.o
src/cfgparse-peers.o src/haterm.o
ifneq ($(TRACE),) ifneq ($(TRACE),)
OBJS += src/calltrace.o OBJS += src/calltrace.o
endif endif
HATERM_OBJS += $(OBJS) src/haterm_init.o
# Used only for forced dependency checking. May be cleared during development. # Used only for forced dependency checking. May be cleared during development.
INCLUDES = $(wildcard include/*/*.h) INCLUDES = $(wildcard include/*/*.h)
DEP = $(INCLUDES) .build_opts DEP = $(INCLUDES) .build_opts
@ -1053,7 +1029,7 @@ IGNORE_OPTS=help install install-man install-doc install-bin \
uninstall clean tags cscope tar git-tar version update-version \ uninstall clean tags cscope tar git-tar version update-version \
opts reg-tests reg-tests-help unit-tests admin/halog/halog dev/flags/flags \ opts reg-tests reg-tests-help unit-tests admin/halog/halog dev/flags/flags \
dev/haring/haring dev/ncpu/ncpu dev/poll/poll dev/tcploop/tcploop \ dev/haring/haring dev/ncpu/ncpu dev/poll/poll dev/tcploop/tcploop \
dev/term_events/term_events dev/gdb/pm-from-core dev/gdb/libs-from-core dev/term_events/term_events
ifneq ($(TARGET),) ifneq ($(TARGET),)
ifeq ($(filter $(firstword $(MAKECMDGOALS)),$(IGNORE_OPTS)),) ifeq ($(filter $(firstword $(MAKECMDGOALS)),$(IGNORE_OPTS)),)
@ -1069,9 +1045,6 @@ endif # non-empty target
haproxy: $(OPTIONS_OBJS) $(OBJS) haproxy: $(OPTIONS_OBJS) $(OBJS)
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
haterm: $(OPTIONS_OBJS) $(HATERM_OBJS)
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
objsize: haproxy objsize: haproxy
$(Q)objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort $(Q)objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort
@ -1087,12 +1060,6 @@ admin/dyncookie/dyncookie: admin/dyncookie/dyncookie.o
dev/flags/flags: dev/flags/flags.o dev/flags/flags: dev/flags/flags.o
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
dev/gdb/libs-from-core: dev/gdb/libs-from-core.o
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
dev/gdb/pm-from-core: dev/gdb/pm-from-core.o
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
dev/haring/haring: dev/haring/haring.o dev/haring/haring: dev/haring/haring.o
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
@ -1146,11 +1113,6 @@ install-doc:
$(INSTALL) -m 644 doc/$$x.txt "$(DESTDIR)$(DOCDIR)" ; \ $(INSTALL) -m 644 doc/$$x.txt "$(DESTDIR)$(DOCDIR)" ; \
done done
install-admin:
$(Q)$(INSTALL) -d "$(DESTDIR)$(SBINDIR)"
$(Q)$(INSTALL) admin/cli/haproxy-dump-certs "$(DESTDIR)$(SBINDIR)"
$(Q)$(INSTALL) admin/cli/haproxy-reload "$(DESTDIR)$(SBINDIR)"
install-bin: install-bin:
$(Q)for i in haproxy $(EXTRA); do \ $(Q)for i in haproxy $(EXTRA); do \
if ! [ -e "$$i" ]; then \ if ! [ -e "$$i" ]; then \
@ -1161,7 +1123,7 @@ install-bin:
$(Q)$(INSTALL) -d "$(DESTDIR)$(SBINDIR)" $(Q)$(INSTALL) -d "$(DESTDIR)$(SBINDIR)"
$(Q)$(INSTALL) haproxy $(EXTRA) "$(DESTDIR)$(SBINDIR)" $(Q)$(INSTALL) haproxy $(EXTRA) "$(DESTDIR)$(SBINDIR)"
install: install-bin install-admin install-man install-doc install: install-bin install-man install-doc
uninstall: uninstall:
$(Q)rm -f "$(DESTDIR)$(MANDIR)"/man1/haproxy.1 $(Q)rm -f "$(DESTDIR)$(MANDIR)"/man1/haproxy.1
@ -1180,7 +1142,6 @@ clean:
$(Q)rm -f addons/51degrees/*.[oas] addons/51degrees/dummy/*.[oas] addons/51degrees/dummy/*/*.[oas] $(Q)rm -f addons/51degrees/*.[oas] addons/51degrees/dummy/*.[oas] addons/51degrees/dummy/*/*.[oas]
$(Q)rm -f addons/deviceatlas/*.[oas] addons/deviceatlas/dummy/*.[oas] addons/deviceatlas/dummy/*.o $(Q)rm -f addons/deviceatlas/*.[oas] addons/deviceatlas/dummy/*.[oas] addons/deviceatlas/dummy/*.o
$(Q)rm -f addons/deviceatlas/dummy/Os/*.o $(Q)rm -f addons/deviceatlas/dummy/Os/*.o
$(Q)rm -f addons/otel/src/*.[oas]
$(Q)rm -f addons/ot/src/*.[oas] $(Q)rm -f addons/ot/src/*.[oas]
$(Q)rm -f addons/wurfl/*.[oas] addons/wurfl/dummy/*.[oas] $(Q)rm -f addons/wurfl/*.[oas] addons/wurfl/dummy/*.[oas]
$(Q)rm -f admin/*/*.[oas] admin/*/*/*.[oas] $(Q)rm -f admin/*/*.[oas] admin/*/*/*.[oas]
@ -1192,7 +1153,7 @@ distclean: clean
$(Q)rm -f admin/dyncookie/dyncookie $(Q)rm -f admin/dyncookie/dyncookie
$(Q)rm -f dev/haring/haring dev/ncpu/ncpu{,.so} dev/poll/poll dev/tcploop/tcploop $(Q)rm -f dev/haring/haring dev/ncpu/ncpu{,.so} dev/poll/poll dev/tcploop/tcploop
$(Q)rm -f dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht $(Q)rm -f dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht
$(Q)rm -f dev/qpack/decode dev/gdb/pm-from-core dev/gdb/libs-from-core $(Q)rm -f dev/qpack/decode
tags: tags:
$(Q)find src include \( -name '*.c' -o -name '*.h' \) -print0 | \ $(Q)find src include \( -name '*.c' -o -name '*.h' \) -print0 | \
@ -1319,8 +1280,6 @@ unit-tests:
# options for all commits within RANGE. RANGE may be either a git range # options for all commits within RANGE. RANGE may be either a git range
# such as ref1..ref2 or a single commit, in which case all commits from # such as ref1..ref2 or a single commit, in which case all commits from
# the master branch to this one will be tested. # the master branch to this one will be tested.
# Will execute TEST_CMD for each commit if defined, and will stop in case of
# failure.
range: range:
$(Q)[ -d .git/. ] || { echo "## Fatal: \"make $@\" may only be used inside a Git repository."; exit 1; } $(Q)[ -d .git/. ] || { echo "## Fatal: \"make $@\" may only be used inside a Git repository."; exit 1; }
@ -1346,8 +1305,6 @@ range:
echo "[ $$index/$$count ] $$commit #############################"; \ echo "[ $$index/$$count ] $$commit #############################"; \
git checkout -q $$commit || die 1; \ git checkout -q $$commit || die 1; \
$(MAKE) all || die 1; \ $(MAKE) all || die 1; \
set -- $(TEST_CMD); \
[ "$$#" -eq 0 ] || "$$@" || die 1; \
index=$$((index + 1)); \ index=$$((index + 1)); \
done; \ done; \
echo;echo "Done! $${count} commit(s) built successfully for RANGE $${RANGE}" ; \ echo;echo "Done! $${count} commit(s) built successfully for RANGE $${RANGE}" ; \

View file

@ -2,6 +2,7 @@
[![alpine/musl](https://github.com/haproxy/haproxy/actions/workflows/musl.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/musl.yml) [![alpine/musl](https://github.com/haproxy/haproxy/actions/workflows/musl.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/musl.yml)
[![AWS-LC](https://github.com/haproxy/haproxy/actions/workflows/aws-lc.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/aws-lc.yml) [![AWS-LC](https://github.com/haproxy/haproxy/actions/workflows/aws-lc.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/aws-lc.yml)
[![openssl no-deprecated](https://github.com/haproxy/haproxy/actions/workflows/openssl-nodeprecated.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/openssl-nodeprecated.yml)
[![Illumos](https://github.com/haproxy/haproxy/actions/workflows/illumos.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/illumos.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) [![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/) [![FreeBSD](https://api.cirrus-ci.com/github/haproxy/haproxy.svg?task=FreeBSD)](https://cirrus-ci.com/github/haproxy/haproxy/)

View file

@ -1,2 +1,2 @@
$Format:%ci$ $Format:%ci$
2026/04/03 2025/05/14

View file

@ -1 +1 @@
3.4-dev8 3.2-dev16

View file

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

View file

@ -31,7 +31,6 @@ static struct {
da_atlas_t atlas; da_atlas_t atlas;
da_evidence_id_t useragentid; da_evidence_id_t useragentid;
da_severity_t loglevel; da_severity_t loglevel;
size_t maxhdrlen;
char separator; char separator;
unsigned char daset:1; unsigned char daset:1;
} global_deviceatlas = { } global_deviceatlas = {
@ -43,7 +42,6 @@ static struct {
.atlasmap = NULL, .atlasmap = NULL,
.atlasfd = -1, .atlasfd = -1,
.useragentid = 0, .useragentid = 0,
.maxhdrlen = 0,
.daset = 0, .daset = 0,
.separator = '|', .separator = '|',
}; };
@ -59,10 +57,6 @@ static int da_json_file(char **args, int section_type, struct proxy *curpx,
return -1; return -1;
} }
global_deviceatlas.jsonpath = strdup(args[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; return 0;
} }
@ -79,7 +73,6 @@ static int da_log_level(char **args, int section_type, struct proxy *curpx,
loglevel = atol(args[1]); loglevel = atol(args[1]);
if (loglevel < 0 || loglevel > 3) { if (loglevel < 0 || loglevel > 3) {
memprintf(err, "deviceatlas log level : expects a log level between 0 and 3, %s given.\n", args[1]); memprintf(err, "deviceatlas log level : expects a log level between 0 and 3, %s given.\n", args[1]);
return -1;
} else { } else {
global_deviceatlas.loglevel = (da_severity_t)loglevel; 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; return -1;
} else { } else {
global_deviceatlas.cookiename = strdup(args[1]); 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); global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
return 0; return 0;
@ -130,7 +119,6 @@ static int da_cache_size(char **args, int section_type, struct proxy *curpx,
cachesize = atol(args[1]); cachesize = atol(args[1]);
if (cachesize < 0 || cachesize > DA_CACHE_MAX) { 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]); memprintf(err, "deviceatlas cache size : expects a cache size between 0 and %d, %s given.\n", DA_CACHE_MAX, args[1]);
return -1;
} else { } else {
#ifdef APINOCACHE #ifdef APINOCACHE
fprintf(stdout, "deviceatlas cache size : no-op, its support is disabled.\n"); 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; da_status_t status;
jsonp = fopen(global_deviceatlas.jsonpath, "r"); 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", ha_alert("deviceatlas : '%s' json file has invalid path or is not readable.\n",
global_deviceatlas.jsonpath); global_deviceatlas.jsonpath);
err_code |= ERR_ALERT | ERR_FATAL; 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, status = da_atlas_compile(jsonp, da_haproxy_read, da_haproxy_seek,
&global_deviceatlas.atlasimgptr, &atlasimglen); &global_deviceatlas.atlasimgptr, &atlasimglen);
fclose(jsonp); fclose(jsonp);
if (unlikely(status != DA_OK)) { if (status != DA_OK) {
ha_alert("deviceatlas : '%s' json file is invalid.\n", ha_alert("deviceatlas : '%s' json file is invalid.\n",
global_deviceatlas.jsonpath); global_deviceatlas.jsonpath);
free(global_deviceatlas.atlasimgptr);
da_fini();
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }
@ -201,10 +187,8 @@ static int init_deviceatlas(void)
status = da_atlas_open(&global_deviceatlas.atlas, extraprops, status = da_atlas_open(&global_deviceatlas.atlas, extraprops,
global_deviceatlas.atlasimgptr, atlasimglen); global_deviceatlas.atlasimgptr, atlasimglen);
if (unlikely(status != DA_OK)) { if (status != DA_OK) {
ha_alert("deviceatlas : data could not be compiled.\n"); ha_alert("deviceatlas : data could not be compiled.\n");
free(global_deviceatlas.atlasimgptr);
da_fini();
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }
@ -213,28 +197,11 @@ static int init_deviceatlas(void)
if (global_deviceatlas.cookiename == 0) { if (global_deviceatlas.cookiename == 0) {
global_deviceatlas.cookiename = strdup(DA_COOKIENAME_DEFAULT); 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.cookienamelen = strlen(global_deviceatlas.cookiename);
} }
global_deviceatlas.useragentid = da_atlas_header_evidence_id(&global_deviceatlas.atlas, global_deviceatlas.useragentid = da_atlas_header_evidence_id(&global_deviceatlas.atlas,
"user-agent"); "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) { 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); global_deviceatlas.atlasmap = mmap(NULL, ATLASTOKSZ, PROT_READ | PROT_WRITE, MAP_SHARED, global_deviceatlas.atlasfd, 0);
if (global_deviceatlas.atlasmap == MAP_FAILED) { if (global_deviceatlas.atlasmap == MAP_FAILED) {
@ -264,22 +231,24 @@ static void deinit_deviceatlas(void)
free(global_deviceatlas.cookiename); free(global_deviceatlas.cookiename);
da_atlas_close(&global_deviceatlas.atlas); da_atlas_close(&global_deviceatlas.atlas);
free(global_deviceatlas.atlasimgptr); free(global_deviceatlas.atlasimgptr);
da_fini();
} }
if (global_deviceatlas.atlasfd != -1) { if (global_deviceatlas.atlasfd != -1) {
munmap(global_deviceatlas.atlasmap, ATLASTOKSZ); munmap(global_deviceatlas.atlasmap, ATLASTOKSZ);
close(global_deviceatlas.atlasfd); close(global_deviceatlas.atlasfd);
shm_unlink(ATLASMAPNM);
} }
da_fini();
} }
static void da_haproxy_checkinst(void) static void da_haproxy_checkinst(void)
{ {
if (global_deviceatlas.atlasmap != 0) { if (global_deviceatlas.atlasmap != 0) {
char *base; char *base;
base = (char *)global_deviceatlas.atlasmap; base = (char *)global_deviceatlas.atlasmap;
if (base[0] != 0) { if (base[0] != 0) {
FILE *jsonp; FILE *jsonp;
void *cnew; void *cnew;
da_status_t status; da_status_t status;
@ -289,10 +258,6 @@ static void da_haproxy_checkinst(void)
da_property_decl_t extraprops[1] = {{NULL, 0}}; da_property_decl_t extraprops[1] = {{NULL, 0}};
#ifdef USE_THREAD #ifdef USE_THREAD
HA_SPIN_LOCK(OTHER_LOCK, &dadwsch_lock); HA_SPIN_LOCK(OTHER_LOCK, &dadwsch_lock);
if (base[0] == 0) {
HA_SPIN_UNLOCK(OTHER_LOCK, &dadwsch_lock);
return;
}
#endif #endif
strlcpy2(atlasp, base + sizeof(char), sizeof(atlasp)); strlcpy2(atlasp, base + sizeof(char), sizeof(atlasp));
jsonp = fopen(atlasp, "r"); jsonp = fopen(atlasp, "r");
@ -310,20 +275,10 @@ static void da_haproxy_checkinst(void)
fclose(jsonp); fclose(jsonp);
if (status == DA_OK) { if (status == DA_OK) {
if (da_atlas_open(&inst, extraprops, cnew, atlassz) == 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); da_atlas_close(&global_deviceatlas.atlas);
free(global_deviceatlas.atlasimgptr); free(global_deviceatlas.atlasimgptr);
global_deviceatlas.atlasimgptr = cnew; global_deviceatlas.atlasimgptr = cnew;
global_deviceatlas.atlas = inst; 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; base[0] = 0;
ha_notice("deviceatlas : new instance, data file date `%s`.\n", ha_notice("deviceatlas : new instance, data file date `%s`.\n",
da_getdatacreationiso8601(&global_deviceatlas.atlas)); da_getdatacreationiso8601(&global_deviceatlas.atlas));
@ -331,8 +286,6 @@ static void da_haproxy_checkinst(void)
ha_alert("deviceatlas : instance update failed.\n"); ha_alert("deviceatlas : instance update failed.\n");
free(cnew); free(cnew);
} }
} else {
free(cnew);
} }
#ifdef USE_THREAD #ifdef USE_THREAD
HA_SPIN_UNLOCK(OTHER_LOCK, &dadwsch_lock); 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) static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_t *devinfo)
{ {
struct buffer *tmp; struct buffer *tmp;
da_propid_t prop; da_propid_t prop, *pprop;
da_status_t status; da_status_t status;
da_type_t proptype; da_type_t proptype;
const char *propname; 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); chunk_appendf(tmp, "%c", global_deviceatlas.separator);
continue; continue;
} }
if (unlikely(da_atlas_getproptype(&global_deviceatlas.atlas, prop, &proptype) != DA_OK)) { pprop = &prop;
chunk_appendf(tmp, "%c", global_deviceatlas.separator); da_atlas_getproptype(&global_deviceatlas.atlas, *pprop, &proptype);
continue;
}
switch (proptype) { switch (proptype) {
case DA_TYPE_BOOLEAN: { case DA_TYPE_BOOLEAN: {
bool val; bool val;
status = da_getpropboolean(devinfo, prop, &val); status = da_getpropboolean(devinfo, *pprop, &val);
if (status == DA_OK) { if (status == DA_OK) {
chunk_appendf(tmp, "%d", val); 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_INTEGER:
case DA_TYPE_NUMBER: { case DA_TYPE_NUMBER: {
long val; long val;
status = da_getpropinteger(devinfo, prop, &val); status = da_getpropinteger(devinfo, *pprop, &val);
if (status == DA_OK) { if (status == DA_OK) {
chunk_appendf(tmp, "%ld", val); 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: { case DA_TYPE_STRING: {
const char *val; const char *val;
status = da_getpropstring(devinfo, prop, &val); status = da_getpropstring(devinfo, *pprop, &val);
if (status == DA_OK) { if (status == DA_OK) {
chunk_appendf(tmp, "%s", val); 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_deviceinfo_t devinfo;
da_status_t status; da_status_t status;
char useragentbuf[1024]; const char *useragent;
char useragentbuf[1024] = { 0 };
int i; 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; return 1;
} }
da_haproxy_checkinst(); da_haproxy_checkinst();
i = smp->data.u.str.data > sizeof(useragentbuf) - 1 ? sizeof(useragentbuf) - 1 : smp->data.u.str.data; i = smp->data.u.str.data > sizeof(useragentbuf) ? sizeof(useragentbuf) : smp->data.u.str.data;
memcpy(useragentbuf, smp->data.u.str.area, i); memcpy(useragentbuf, smp->data.u.str.area, i - 1);
useragentbuf[i] = 0; useragentbuf[i - 1] = 0;
useragent = (const char *)useragentbuf;
status = da_search(&global_deviceatlas.atlas, &devinfo, 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); 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) 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 channel *chn;
struct htx *htx; struct htx *htx;
struct htx_blk *blk; struct htx_blk *blk;
char vbuf[DA_MAX_HEADERS][1024]; char vbuf[DA_MAX_HEADERS][1024] = {{ 0 }};
int i, nbh = 0; int i, nbh = 0;
if (unlikely(global_deviceatlas.daset == 0)) { if (global_deviceatlas.daset == 0) {
return 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); chn = (smp->strm ? &smp->strm->req : NULL);
htx = smp_prefetch_htx(smp, chn, NULL, 1); htx = smp_prefetch_htx(smp, chn, NULL, 1);
if (unlikely(!htx)) if (!htx)
return 0; return 0;
i = 0;
for (blk = htx_get_first_blk(htx); nbh < DA_MAX_HEADERS && blk; blk = htx_get_next_blk(htx, blk)) { for (blk = htx_get_first_blk(htx); nbh < DA_MAX_HEADERS && blk; blk = htx_get_next_blk(htx, blk)) {
size_t vlen; size_t vlen;
char *pval; char *pval;
da_evidence_id_t evid; da_evidence_id_t evid;
enum htx_blk_type type; enum htx_blk_type type;
struct ist n, v; struct ist n, v;
char hbuf[64]; char hbuf[24] = { 0 };
char tval[1024]; char tval[1024] = { 0 };
type = htx_get_blk_type(blk); 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; 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; continue;
} }
memcpy(hbuf, n.ptr, n.len); memcpy(hbuf, n.ptr, n.len);
hbuf[n.len] = 0; hbuf[n.len] = 0;
pval = v.ptr;
vlen = v.len;
evid = -1; evid = -1;
i = v.len > sizeof(tval) - 1 ? sizeof(tval) - 1 : v.len; i = v.len > sizeof(tval) - 1 ? sizeof(tval) - 1 : v.len;
memcpy(tval, v.ptr, i); memcpy(tval, v.ptr, i);
tval[i] = 0; tval[i] = 0;
pval = tval; pval = tval;
vlen = i;
if (strcasecmp(hbuf, "Accept-Language") == 0) { if (strcasecmp(hbuf, "Accept-Language") == 0) {
evid = da_atlas_accept_language_evidence_id(&global_deviceatlas.atlas); 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; continue;
} }
vlen = pl; vlen -= global_deviceatlas.cookienamelen - 1;
pval = p; pval = p;
evid = da_atlas_clientprop_evidence_id(&global_deviceatlas.atlas); evid = da_atlas_clientprop_evidence_id(&global_deviceatlas.atlas);
} else { } else {

View file

@ -141,11 +141,6 @@ enum {
DA_INITIAL_MEMORY_ESTIMATE = 1024 * 1024 * 14 DA_INITIAL_MEMORY_ESTIMATE = 1024 * 1024 * 14
}; };
struct header_evidence_entry {
const char *name;
da_evidence_id_t id;
};
struct da_config { struct da_config {
unsigned int cache_size; unsigned int cache_size;
unsigned int __reserved[15]; /* enough reserved keywords for future use */ unsigned int __reserved[15]; /* enough reserved keywords for future use */

View file

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

View file

@ -35,11 +35,11 @@
do { \ do { \
if (!(l) || (flt_ot_debug.level & (1 << (l)))) \ if (!(l) || (flt_ot_debug.level & (1 << (l)))) \
(void)fprintf(stderr, FLT_OT_DBG_FMT("%.*s" f "\n"), \ (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) } 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_FUNC(f, ...) do { FLT_OT_DBG(1, "%s(" f ") {", __func__, ##__VA_ARGS__); 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(a) do { dbg_indent_level -= 3; FLT_OT_DBG(1, "}"); return a; } while (0)
# define FLT_OT_RETURN_EX(a,t,f) do { flt_ot_dbg_indent_level -= 3; { t _r = (a); FLT_OT_DBG(1, "} = " f, _r); return _r; } } while (0) # define FLT_OT_RETURN_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_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_RETURN_PTR(a) FLT_OT_RETURN_EX((a), void *, "%p")
# define FLT_OT_DBG_IFDEF(a,b) a # 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; extern struct flt_ot_debug flt_ot_debug;
#else #else

View file

@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 }, #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 * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 }, #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 * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
#include "../include/include.h" #include "include.h"
#ifdef DEBUG_OT #ifdef DEBUG_OT

View file

@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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; 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 * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
#include "../include/include.h" #include "include.h"
#ifdef DEBUG_OT #ifdef DEBUG_OT
struct flt_ot_debug flt_ot_debug; struct flt_ot_debug flt_ot_debug;
THREAD_LOCAL int flt_ot_dbg_indent_level = 0; THREAD_LOCAL int dbg_indent_level = 0;
#endif #endif
#ifdef OTC_DBG_MEM #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) 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)); 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) if (flt_ot_conf_str_init(args[i], linenum, head, err) == NULL)
retval |= ERR_ABORT | ERR_ALERT; retval |= ERR_ABORT | ERR_ALERT;
if (retval & ERR_CODE)
flt_ot_conf_str_free(&str);
FLT_OT_RETURN_INT(retval); 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) { 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); 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; retval |= ERR_ABORT | ERR_ALERT;
} }
else if (pdata->keyword == FLT_OT_PARSE_GROUP_SCOPES) { else if (pdata->keyword == FLT_OT_PARSE_GROUP_SCOPES) {

View file

@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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; static struct pool_head *pool_head_ot_scope_span __read_mostly = NULL;

View file

@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
#include "../include/include.h" #include "include.h"
#ifdef DEBUG_OT #ifdef DEBUG_OT
@ -41,7 +41,7 @@ void flt_ot_args_dump(char **args)
argc = flt_ot_args_count(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++) for (i = 1; i < argc; i++)
(void)fprintf(stderr, "'%s' ", args[i]); (void)fprintf(stderr, "'%s' ", args[i]);

View file

@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
#include "../include/include.h" #include "include.h"
#ifdef DEBUG_OT #ifdef DEBUG_OT
@ -46,10 +46,10 @@ static void flt_ot_vars_scope_dump(struct vars *vars, const char *scope)
vars_rdlock(vars); vars_rdlock(vars);
for (i = 0; i < VAR_NAME_ROOTS; i++) { for (i = 0; i < VAR_NAME_ROOTS; i++) {
struct ceb_node *node = cebu64_imm_first(&(vars->name_root[i])); struct ceb_node *node = cebu64_first(&(vars->name_root[i]));
for ( ; node != NULL; node = cebu64_imm_next(&(vars->name_root[i]), node)) { for ( ; node != NULL; node = cebu64_next(&(vars->name_root[i]), node)) {
struct var *var = container_of(node, struct var, name_node); struct var *var = container_of(node, struct var, 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))); 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)));
} }

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
----------------------------------------------------------------------

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 270.35us 136.92us 16.02ms 97.98%
Req/Sec 3.77k 217.57 5.33k 67.74%
Latency Distribution
50% 264.00us
75% 287.00us
90% 309.00us
99% 494.00us
9012538 requests in 5.00m, 2.17GB read
Requests/sec: 30031.85
Transfer/sec: 7.42MB
----------------------------------------------------------------------
--- rate-limit 75.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 242.56us 131.47us 9.82ms 94.77%
Req/Sec 4.21k 218.42 5.61k 68.12%
Latency Distribution
50% 246.00us
75% 279.00us
90% 308.00us
99% 464.00us
10050409 requests in 5.00m, 2.42GB read
Requests/sec: 33490.26
Transfer/sec: 8.27MB
----------------------------------------------------------------------
--- rate-limit 50.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 215.92us 130.82us 9.93ms 96.81%
Req/Sec 4.73k 243.84 7.13k 67.13%
Latency Distribution
50% 208.00us
75% 264.00us
90% 300.00us
99% 439.00us
11307386 requests in 5.00m, 2.73GB read
Requests/sec: 37678.82
Transfer/sec: 9.31MB
----------------------------------------------------------------------
--- rate-limit 25.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 192.36us 132.47us 13.75ms 96.79%
Req/Sec 5.33k 260.46 6.17k 66.30%
Latency Distribution
50% 166.00us
75% 227.00us
90% 280.00us
99% 407.00us
12734770 requests in 5.00m, 3.07GB read
Requests/sec: 42448.91
Transfer/sec: 10.48MB
----------------------------------------------------------------------
--- rate-limit 10.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 180.08us 127.35us 13.34ms 97.06%
Req/Sec 5.71k 272.98 6.40k 67.94%
Latency Distribution
50% 161.00us
75% 183.00us
90% 250.00us
99% 386.00us
13641901 requests in 5.00m, 3.29GB read
Requests/sec: 45457.92
Transfer/sec: 11.23MB
----------------------------------------------------------------------
--- rate-limit 2.5 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 171.64us 107.08us 5.69ms 96.57%
Req/Sec 5.97k 289.99 6.55k 68.53%
Latency Distribution
50% 159.00us
75% 171.00us
90% 195.00us
99% 372.00us
14268464 requests in 5.00m, 3.44GB read
Requests/sec: 47545.77
Transfer/sec: 11.74MB
----------------------------------------------------------------------
--- 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.86us 104.53us 5.75ms 97.73%
Req/Sec 6.07k 282.19 6.59k 67.47%
Latency Distribution
50% 158.00us
75% 168.00us
90% 186.00us
99% 361.00us
14498699 requests in 5.00m, 3.50GB read
Requests/sec: 48312.96
Transfer/sec: 11.93MB
----------------------------------------------------------------------
--- rate-limit disabled --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 168.36us 129.52us 17.68ms 98.13%
Req/Sec 6.11k 263.70 6.83k 70.42%
Latency Distribution
50% 157.00us
75% 167.00us
90% 183.00us
99% 363.00us
14590953 requests in 5.00m, 3.52GB read
Requests/sec: 48620.36
Transfer/sec: 12.01MB
----------------------------------------------------------------------
--- rate-limit off --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 168.73us 120.51us 15.03ms 98.02%
Req/Sec 6.09k 270.88 6.55k 68.99%
Latency Distribution
50% 158.00us
75% 167.00us
90% 185.00us
99% 360.00us
14538507 requests in 5.00m, 3.51GB read
Requests/sec: 48445.53
Transfer/sec: 11.97MB
----------------------------------------------------------------------

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 238.75us 129.45us 15.59ms 98.45%
Req/Sec 4.27k 75.22 5.28k 77.45%
Latency Distribution
50% 228.00us
75% 243.00us
90% 262.00us
99% 410.00us
10206938 requests in 5.00m, 2.46GB read
Requests/sec: 34011.80
Transfer/sec: 8.40MB
----------------------------------------------------------------------
--- rate-limit 75.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 230.08us 201.50us 32.12ms 99.25%
Req/Sec 4.46k 83.43 5.36k 75.61%
Latency Distribution
50% 222.00us
75% 241.00us
90% 261.00us
99% 401.00us
10641998 requests in 5.00m, 2.57GB read
Requests/sec: 35461.59
Transfer/sec: 8.76MB
----------------------------------------------------------------------
--- rate-limit 50.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 222.33us 242.52us 35.13ms 99.43%
Req/Sec 4.62k 99.86 5.78k 79.00%
Latency Distribution
50% 211.00us
75% 237.00us
90% 259.00us
99% 400.00us
11046951 requests in 5.00m, 2.66GB read
Requests/sec: 36810.91
Transfer/sec: 9.09MB
----------------------------------------------------------------------
--- rate-limit 25.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 210.95us 117.20us 10.57ms 97.85%
Req/Sec 4.84k 101.85 6.04k 68.10%
Latency Distribution
50% 198.00us
75% 222.00us
90% 252.00us
99% 394.00us
11551741 requests in 5.00m, 2.79GB read
Requests/sec: 38493.03
Transfer/sec: 9.51MB
----------------------------------------------------------------------
--- rate-limit 10.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 206.15us 209.34us 31.73ms 99.27%
Req/Sec 4.99k 112.62 5.36k 71.21%
Latency Distribution
50% 193.00us
75% 210.00us
90% 237.00us
99% 387.00us
11924489 requests in 5.00m, 2.88GB read
Requests/sec: 39735.09
Transfer/sec: 9.81MB
----------------------------------------------------------------------
--- rate-limit 2.5 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 202.35us 180.56us 27.28ms 99.10%
Req/Sec 5.08k 145.06 8.27k 71.24%
Latency Distribution
50% 191.00us
75% 205.00us
90% 223.00us
99% 374.00us
12131047 requests in 5.00m, 2.93GB read
Requests/sec: 40423.43
Transfer/sec: 9.98MB
----------------------------------------------------------------------
--- rate-limit 0.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 200.88us 223.19us 32.70ms 99.44%
Req/Sec 5.13k 151.03 6.55k 69.46%
Latency Distribution
50% 190.00us
75% 203.00us
90% 218.00us
99% 367.00us
12256706 requests in 5.00m, 2.96GB read
Requests/sec: 40842.16
Transfer/sec: 10.09MB
----------------------------------------------------------------------
--- rate-limit disabled --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 194.91us 222.55us 33.80ms 99.47%
Req/Sec 5.29k 167.52 5.95k 68.32%
Latency Distribution
50% 184.00us
75% 197.00us
90% 214.00us
99% 353.00us
12633928 requests in 5.00m, 3.05GB read
Requests/sec: 42112.54
Transfer/sec: 10.40MB
----------------------------------------------------------------------
--- rate-limit off --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 194.37us 166.76us 28.75ms 99.09%
Req/Sec 5.28k 160.02 5.86k 68.02%
Latency Distribution
50% 184.00us
75% 197.00us
90% 214.00us
99% 355.00us
12622896 requests in 5.00m, 3.04GB read
Requests/sec: 42062.31
Transfer/sec: 10.39MB
----------------------------------------------------------------------

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 213.08us 136.99us 17.58ms 98.10%
Req/Sec 4.80k 251.04 5.97k 68.01%
Latency Distribution
50% 203.00us
75% 223.00us
90% 245.00us
99% 405.00us
11464278 requests in 5.00m, 2.77GB read
Requests/sec: 38201.61
Transfer/sec: 9.44MB
----------------------------------------------------------------------
--- rate-limit 75.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 202.18us 121.42us 12.04ms 97.72%
Req/Sec 5.05k 248.48 5.85k 65.69%
Latency Distribution
50% 194.00us
75% 219.00us
90% 245.00us
99% 393.00us
12071015 requests in 5.00m, 2.91GB read
Requests/sec: 40223.31
Transfer/sec: 9.94MB
----------------------------------------------------------------------
--- rate-limit 50.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 190.49us 117.32us 7.33ms 97.62%
Req/Sec 5.37k 265.60 6.98k 65.98%
Latency Distribution
50% 181.00us
75% 208.00us
90% 237.00us
99% 383.00us
12837427 requests in 5.00m, 3.10GB read
Requests/sec: 42777.17
Transfer/sec: 10.57MB
----------------------------------------------------------------------
--- rate-limit 25.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 180.46us 123.40us 13.20ms 97.80%
Req/Sec 5.69k 242.93 7.75k 68.66%
Latency Distribution
50% 165.00us
75% 194.00us
90% 223.00us
99% 375.00us
13595213 requests in 5.00m, 3.28GB read
Requests/sec: 45302.34
Transfer/sec: 11.19MB
----------------------------------------------------------------------
--- rate-limit 10.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 174.69us 129.17us 16.50ms 97.93%
Req/Sec 5.89k 260.40 7.03k 69.57%
Latency Distribution
50% 160.00us
75% 178.00us
90% 210.00us
99% 374.00us
14068388 requests in 5.00m, 3.39GB read
Requests/sec: 46879.07
Transfer/sec: 11.58MB
----------------------------------------------------------------------
--- rate-limit 2.5 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 170.58us 116.89us 11.49ms 97.74%
Req/Sec 6.03k 249.35 6.54k 67.44%
Latency Distribution
50% 158.00us
75% 170.00us
90% 192.00us
99% 375.00us
14402604 requests in 5.00m, 3.47GB read
Requests/sec: 47992.71
Transfer/sec: 11.85MB
----------------------------------------------------------------------
--- rate-limit 0.0 --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 167.96us 114.36us 10.99ms 97.81%
Req/Sec 6.12k 266.16 6.57k 70.70%
Latency Distribution
50% 157.00us
75% 166.00us
90% 183.00us
99% 370.00us
14622790 requests in 5.00m, 3.53GB read
Requests/sec: 48726.40
Transfer/sec: 12.04MB
----------------------------------------------------------------------
--- rate-limit disabled --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 167.74us 114.11us 11.10ms 97.82%
Req/Sec 6.13k 251.71 6.57k 69.59%
Latency Distribution
50% 157.00us
75% 166.00us
90% 182.00us
99% 368.00us
14641307 requests in 5.00m, 3.53GB read
Requests/sec: 48788.18
Transfer/sec: 12.05MB
----------------------------------------------------------------------
--- rate-limit off --------------------------------------------------
Running 5m test @ http://localhost:10080/index.html
8 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 168.00us 112.83us 11.07ms 97.79%
Req/Sec 6.12k 264.12 7.23k 68.95%
Latency Distribution
50% 157.00us
75% 166.00us
90% 183.00us
99% 369.00us
14613970 requests in 5.00m, 3.53GB read
Requests/sec: 48697.01
Transfer/sec: 12.03MB
----------------------------------------------------------------------

View file

@ -1,319 +0,0 @@
-----------------------------------------
HAProxy OTEL filter speed test guide
Version 1.0
( Last update: 2026-04-09 )
-----------------------------------------
Author : Miroslav Zagorac
Contact : mzagorac at haproxy dot com
SUMMARY
--------
1. Overview
2. Prerequisites
3. Running the test
4. Test parameters
5. Rate-limit levels
6. Test configurations
7. Results
7.1. Standalone (sa)
7.2. Comparison (cmp)
7.3. Context propagation (ctx)
7.4. Frontend / backend (fe-be)
8. Summary
1. Overview
------------
The test-speed.sh script measures the performance impact of the OTEL filter on
HAProxy at various rate-limit settings. For each test configuration, the script
iterates through a series of rate-limit values -- from full tracing (100%) down
to the filter being completely removed -- measuring throughput and latency at
each level.
The script uses template files (haproxy.cfg.in and otel.cfg.in) from each test
directory to generate the actual configuration files. A sed substitution
adjusts the rate-limit value (or disables/removes the filter) before each run.
2. Prerequisites
-----------------
The following tools must be installed and available in PATH:
- thttpd : a lightweight HTTP server used as the backend origin server.
It serves a small static HTML file (index.html) on port 8000.
- wrk : an HTTP benchmarking tool that generates the test load.
See https://github.com/wg/wrk
3. Running the test
--------------------
The test is executed from the test directory. It can be run for all
configurations at once or for a single configuration.
To run all configurations:
% ./test-speed.sh all
This produces result files in the _logs directory:
_logs/README-speed-fe-be
_logs/README-speed-sa
_logs/README-speed-cmp
_logs/README-speed-ctx
To run a single configuration:
% ./test-speed.sh <cfg> [<dir>]
Where <cfg> corresponds to a run-<cfg>.sh script and <dir> is the configuration
directory (defaults to <cfg>). For example:
% ./test-speed.sh sa
% ./test-speed.sh fe-be fe
% ./test-speed.sh cmp
% ./test-speed.sh ctx
4. Test parameters
-------------------
The wrk benchmarking tool is invoked with the following parameters:
-t8 8 threads
-c8 8 concurrent connections
-d300 5-minute test duration (300 seconds)
--latency latency distribution reporting
Each rate-limit level is tested sequentially. Between runs, HAProxy is stopped
via SIGUSR1 and restarted with the next rate-limit configuration. A 10-second
pause separates consecutive runs.
The backend origin server (thttpd) serves a small static HTML page (index.html,
approximately 50 bytes) on port 8000. HAProxy listens on port 10080 and proxies
requests to the origin.
5. Rate-limit levels
---------------------
The script tests nine rate-limit levels in the following order:
100.0 - the filter processes every stream (worst case)
75.0 - the filter processes 75% of streams
50.0 - the filter processes 50% of streams
25.0 - the filter processes 25% of streams
10.0 - the filter processes 10% of streams
2.5 - the filter processes 2.5% of streams
0.0 - the filter is loaded and attached to every stream but never
processes any telemetry (the rate-limit check always fails);
this measures the per-stream attach and detach overhead
disabled - the filter is loaded but disabled via 'option disabled'; it is not
attached to streams at all; this measures the cost of loading and
initializing the filter library without any per-stream work
off - the 'filter opentelemetry' and 'otel-group' directives are
commented out of haproxy.cfg; the filter is not loaded and has zero
presence in the processing path; this is the absolute baseline
In the result tables, the 'overhead' column is the throughput loss relative to
the 'off' baseline, expressed as a percentage:
overhead = (req/s_off - req/s_test) / req/s_off * 100
6. Test configurations
-----------------------
Four OTEL filter configurations are tested. They differ in complexity and in
the features they exercise:
sa - Standalone. Uses all possible HAProxy filter events with spans,
attributes, events, links, baggage, status, metrics and groups.
This is the most comprehensive single-instance configuration and
represents the worst-case scenario.
cmp - Comparison. A simplified configuration made for comparison with
other tracing implementations. It uses a reduced span hierarchy
without context propagation, groups or metrics. This is closer to
a typical production deployment.
ctx - Context propagation. Similar to 'sa' in scope coverage, but spans
are opened using extracted span contexts (inject/extract via 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.
fe-be - Frontend / backend. Two cascaded HAProxy instances: the frontend
(fe) creates the root trace and injects span context into HTTP
headers; the backend (be) extracts the context and continues the
trace. This configuration measures the combined overhead of two
OTEL filter instances plus the inter-process context propagation
cost.
Note: the rate-limit is varied only on the frontend. The backend
always runs with its default configuration (filter enabled,
hard-errors on). The backend configuration is only modified for
the 'disabled' and 'off' levels.
7. Results
-----------
The tables below summarize the benchmarking results. The 'req/s' column shows
the sustained request rate reported by wrk, 'avg latency' is the average
response time, and 'overhead' is the throughput loss relative to the 'off'
baseline.
7.1. Standalone (sa)
---------------------
---------------------------------------------------------------
rate-limit req/s avg latency overhead
---------------------------------------------------------------
100.0% 38,202 213.08 us 21.6%
75.0% 40,223 202.18 us 17.4%
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%
0.0% 48,726 167.96 us ~0
disabled 48,788 167.74 us ~0
off 48,697 168.00 us baseline
---------------------------------------------------------------
With all possible events active, the sa configuration at 100% rate-limit shows
a 22% throughput reduction. At 10% rate-limit the overhead drops to 3.7%, and
at 2.5% it is barely measurable at 1.4%. The 'disabled' and '0.0' levels show
no measurable overhead above the baseline.
7.2. Comparison (cmp)
----------------------
---------------------------------------------------------------
rate-limit req/s avg latency overhead
---------------------------------------------------------------
100.0% 44,780 182.58 us 7.6%
75.0% 45,362 180.10 us 6.4%
50.0% 46,279 176.59 us 4.6%
25.0% 47,061 173.57 us 2.9%
10.0% 47,793 170.85 us 1.4%
2.5% 48,410 169.11 us 0.2%
0.0% 48,586 168.09 us ~0
disabled 48,510 168.57 us ~0
off 48,488 168.64 us baseline
---------------------------------------------------------------
The simplified cmp configuration shows significantly lower overhead than sa at
every rate-limit level. At 100% rate-limit the overhead is 7.6%, at 10% it is
1.4%, and below 2.5% it becomes indistinguishable from the baseline.
7.3. Context propagation (ctx)
-------------------------------
---------------------------------------------------------------
rate-limit req/s avg latency overhead
---------------------------------------------------------------
100.0% 30,032 270.35 us 38.0%
75.0% 33,490 242.56 us 30.9%
50.0% 37,679 215.92 us 22.2%
25.0% 42,449 192.36 us 12.4%
10.0% 45,458 180.08 us 6.2%
2.5% 47,546 171.64 us 1.9%
0.0% 48,313 168.86 us 0.3%
disabled 48,620 168.36 us ~0
off 48,446 168.73 us baseline
---------------------------------------------------------------
The ctx configuration is the most expensive due to the inject/extract cycle on
every scope execution. At 100% rate-limit the overhead reaches 38%. However,
the cost scales linearly with the rate-limit: at 10% the overhead is 6.2%, and
at 2.5% it drops to 1.9%. The filter attachment overhead (0.0% vs off) is
negligible at 0.3%.
7.4. Frontend / backend (fe-be)
--------------------------------
---------------------------------------------------------------
rate-limit req/s avg latency overhead
---------------------------------------------------------------
100.0% 34,012 238.75 us 19.1%
75.0% 35,462 230.08 us 15.7%
50.0% 36,811 222.33 us 12.5%
25.0% 38,493 210.95 us 8.5%
10.0% 39,735 206.15 us 5.5%
2.5% 40,423 202.35 us 3.9%
0.0% 40,842 200.88 us 2.9%
disabled 42,113 194.91 us ~0
off 42,062 194.37 us baseline
---------------------------------------------------------------
The fe-be configuration involves two HAProxy instances in series, so the
absolute baseline (off) is already lower at 42,062 req/s due to the extra
network hop. The rate-limit is varied only on the frontend; the backend
always has the filter loaded with hard-errors enabled.
This explains the 2.9% overhead at rate-limit 0.0: even though the frontend
never traces, the backend filter still attaches to every stream, attempts to
extract context from the HTTP headers, fails (because the frontend did not
inject any context), and the hard-errors option stops further processing.
This per-stream attach/extract/error cycle accounts for the residual cost.
When both instances have the filter disabled (disabled level), the overhead
is within measurement noise, consistent with the single-instance
configurations.
8. Summary
-----------
The table below shows the overhead for each configuration at selected rate-limit
levels:
---------------------------------------------------
rate-limit sa cmp ctx fe-be
---------------------------------------------------
100.0% 21.6% 7.6% 38.0% 19.1%
25.0% 7.0% 2.9% 12.4% 8.5%
10.0% 3.7% 1.4% 6.2% 5.5%
2.5% 1.4% 0.2% 1.9% 3.9%
---------------------------------------------------
Key observations:
- The overhead scales approximately linearly with the rate-limit value.
Reducing the rate-limit from 100% to 10% eliminates the vast majority
of the cost in all configurations.
- The cmp configuration, which uses a reduced span hierarchy typical of
production deployments, adds only 1.4% overhead at a 10% rate-limit.
- The sa configuration, which exercises all possible events, stays at about
7% overhead at a 25% rate-limit and below 4% at 10%.
- The ctx configuration is the most expensive due to the inject/extract
context propagation on every scope. It is designed as a stress test for
the propagation mechanism rather than a practical production template.
- The fe-be configuration carries a higher fixed cost because two HAProxy
instances are involved and the backend filter processes context extraction
regardless of the frontend rate-limit setting.
- Loading the filter but disabling it via 'option disabled' adds no measurable
overhead in any configuration.
- The filter attachment cost without any telemetry processing (rate-limit 0.0)
is 0.3% or less for single-instance configurations (sa, cmp, ctx).
- In typical production use with a rate-limit of 10% or less, the performance
impact of the OTEL filter should be negligible for single-instance deployments.

View file

@ -1,19 +0,0 @@
global
stats socket /tmp/haproxy-be.sock mode 666 level admin
listen stats
mode http
bind *:8002
stats uri /
stats admin if TRUE
stats refresh 10s
frontend otel-test-be-frontend
bind *:11080
default_backend servers-backend
# OTel filter
filter opentelemetry id otel-test-be config be/otel.cfg
backend servers-backend
server server-1 127.0.0.1:8000

View file

@ -1,61 +0,0 @@
[otel-test-be]
otel-instrumentation otel-test-instrumentation
config be/otel.yml
# log localhost:514 local7 debug
option dontlog-normal
option hard-errors
no option disabled
scopes frontend_http_request
scopes backend_tcp_request
scopes backend_http_request
scopes client_session_end
scopes server_session_start
scopes tcp_response
scopes http_response
scopes server_session_end
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
otel-scope backend_tcp_request
span "Backend TCP request" parent "Frontend HTTP request"
finish "Frontend HTTP request"
otel-event on-backend-tcp-request
otel-scope backend_http_request
span "Backend HTTP request" parent "Backend TCP request"
finish "Backend TCP request"
otel-event on-backend-http-request
otel-scope client_session_end
finish "Client session"
otel-event on-client-session-end
otel-scope server_session_start
span "Server session" parent "HAProxy session"
finish "Backend HTTP request"
otel-event on-server-session-start
otel-scope tcp_response
span "TCP response" parent "Server session"
otel-event on-tcp-response
otel-scope http_response
span "HTTP response" parent "TCP response"
attribute "http.status_code" status
finish "TCP response"
otel-event on-http-response
otel-scope server_session_end
finish *
otel-event on-server-session-end

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