From: Marc-Andre Casavant Date: Thu, 23 Apr 2026 13:47:28 +0000 (-0400) Subject: Four new valgrind profiling multi-server tests X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b1fc3ad0804280e095b1ea591dda23b1921e9b68;p=thirdparty%2Ffreeradius-server.git Four new valgrind profiling multi-server tests --- diff --git a/.gitignore b/.gitignore index 43a846c03b5..42f4e66905a 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,6 @@ main-site.yml # Claude AI build-2f* + +# Profiling results +prof-results/ diff --git a/src/tests/multi-server/README.md b/src/tests/multi-server/README.md index 57a29ebe660..2ef90d28a1a 100644 --- a/src/tests/multi-server/README.md +++ b/src/tests/multi-server/README.md @@ -29,7 +29,7 @@ make test.multi-server.ci ### A specific test ```bash -make test.multi-server.proxy-accept.short.ci +make test.multi-server.proxy-accept.short_ci ``` ### Parallel execution @@ -80,3 +80,60 @@ finding `*.test.yml` files within them. - `*.test.yml` - Test parameter file (discovered by `make test.multi-server`) - `*.ci.test.yml` - CI test parameter file (also discovered by `make test.multi-server.ci`) - `*.yml.j2` - Jinja2 template (rendered, not treated as a test) + +# Multi-Server Profiling Tests + +Four suites instrument FreeRADIUS under Valgrind to collect heap and call-graph profiles: + +| Suite | What it exercises | +| --- | --- | +| `prof-accept` | Plain RADIUS accept (no external services) | +| `prof-pap-auth` | PAP authentication | +| `prof-ldap` | Authentication backed by an LDAP server | +| `prof-mysql` | Authentication backed by a MySQL database | + +Results land in `prof-results//////`. + +### Docker image dependencies + +Profiling suites require images that are not needed by regular multi-server tests. +The `freeradius-prof.image` target builds or verifies them automatically before any `prof-*` test runs: + +- `freeradius40x-build/ubuntu24:latest` — crossbuild base image +- `freeradius4-/ubuntu24:latest` — FreeRADIUS profiling base image +- `freeradius-prof:latest` — final multi-server profiling image (built by `build_image.sh`) + +The `prof-ldap` suite additionally requires: + +- `freeradius4/openldap-prof:latest` — built via the `openldap.image` target + +To build all profiling images explicitly: + +```bash +make freeradius-prof.image +make openldap.image # prof-ldap only +``` + +### Running on Linux + +Profiling tests run the same way as any other multi-server test: + +```bash +make test.multi-server.prof-mysql.short_ci +``` + +### Running on macOS (Apple Silicon) + +The profiling image is based on a `crossbuild.` base image that is built for +`linux/amd64`. On Apple Silicon you must pass `BUILD_PLATFORM=linux/amd64` so that +Docker pulls and runs the correct platform variant: + +```bash +make test.multi-server.prof-mysql.short_ci BUILD_PLATFORM=linux/amd64 +``` + +This applies to all four profiling suites and to the image-build targets as well: + +```bash +make freeradius-prof.image BUILD_PLATFORM=linux/amd64 +``` diff --git a/src/tests/multi-server/all.mk b/src/tests/multi-server/all.mk index b911ef3342c..846146bf249 100644 --- a/src/tests/multi-server/all.mk +++ b/src/tests/multi-server/all.mk @@ -7,12 +7,16 @@ # # Usage: # make -f src/tests/multi-server/all.mk test.multi-server # run all tests -# make -f src/tests/multi-server/all.mk test.multi-server.5hs-autoaccept.short # run single test +# make -f src/tests/multi-server/all.mk test.multi-server.ci # run all ci tests +# make -f src/tests/multi-server/all.mk test.multi-server.proxy-accept.short_ci # run single test # make -f src/tests/multi-server/all.mk clean.test.multi-server # clean logs # SHELL := /bin/bash +PROFILE ?= default-profiling +BUILD_PLATFORM ?= + # # Allow for stand-alone builds from the local directory. # @@ -28,6 +32,10 @@ endif DIR := $(abspath ${top_srcdir}/src/tests/multi-server) OUTPUT := $(abspath $(BUILD_DIR)/tests/multi-server) +GIT_BRANCH := $(or $(shell git -C $(top_srcdir) rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '_'),unknown-branch) +GIT_COMMIT := $(or $(shell git -C $(top_srcdir) rev-parse --short HEAD 2>/dev/null),unknown-commit) +PROF_RESULTS_ROOT := $(abspath $(top_srcdir)/prof-results) + # FIXME: We should be using packaged versions of the multi-server test framework # instead of cloning from git. TEST_MULTI_SERVER_GIT_REPO := https://github.com/InkbridgeNetworks/radenv.git @@ -139,6 +147,9 @@ TEST_MULTI_SERVER_RENDERED.${1}.${2} := $$(patsubst $$(DIR)/tests/${1}/%.j2, $$(foreach j,$$(TEST_MULTI_SERVER_JINJA_FILES.${1}.${2}),$$(eval $$(call TEST_MULTI_SERVER_RENDER,${1},${2},${3},$$j))) +.PHONY: render.test.multi-server.${1}.${2} +render.test.multi-server.${1}.${2}: $$(TEST_MULTI_SERVER_RENDERED.${1}.${2}) + .PHONY: test.multi-server.${1}.${2} test.multi-server.${1}.${2}: $$(TEST_MULTI_SERVER_RENDERED.${1}.${2}) $$(eval CMD := cd $(TEST_MULTI_SERVER_FRAMEWORK_DIR) && . .venv/bin/activate && DATA_PATH="${4}" python3 -m src.multi_server_test $(TEST_MULTI_SERVER_FLAGS) --project-name "${1}-${2}" --compose "${4}/environment.yml" --test "${4}/template.yml" --use-files --listener-dir "${4}/listener" --log-dir "${4}/logs" --output "${4}/logs/result.log") @@ -165,19 +176,72 @@ test.multi-server.${1}.${2}: $$(TEST_MULTI_SERVER_RENDERED.${1}.${2}) } endef +# +# TEST_MULTI_SERVER_PROF_INSTANCE - like TEST_MULTI_SERVER_INSTANCE but +# computes PROF_RESULTS_PATH and exports it to docker compose so valgrind +# output lands in: +# $(PROF_RESULTS_ROOT)///// +# +# ${1} = suite dir name +# ${2} = test name +# ${3} = params file path +# ${4} = test output directory +# +define TEST_MULTI_SERVER_PROF_INSTANCE +TEST_MULTI_SERVER_JINJA_FILES.${1}.${2} := $$(wildcard $$(DIR)/tests/${1}/*.j2) +TEST_MULTI_SERVER_RENDERED.${1}.${2} := $$(patsubst $$(DIR)/tests/${1}/%.j2,${4}/%,$$(TEST_MULTI_SERVER_JINJA_FILES.${1}.${2})) + +$$(foreach j,$$(TEST_MULTI_SERVER_JINJA_FILES.${1}.${2}),$$(eval $$(call TEST_MULTI_SERVER_RENDER,${1},${2},${3},$$j))) + +.PHONY: render.test.multi-server.${1}.${2} +render.test.multi-server.${1}.${2}: $$(TEST_MULTI_SERVER_RENDERED.${1}.${2}) + +.PHONY: test.multi-server.${1}.${2} +test.multi-server.${1}.${2}: $$(TEST_MULTI_SERVER_RENDERED.${1}.${2}) + ${Q}mkdir -p "${4}/logs" "${4}/listener" + ${Q}echo "MULTI-SERVER-TEST test.multi-server.${1}.${2}" + ${Q}PROF_BASE="$(PROF_RESULTS_ROOT)/${1}/${2}/$(GIT_BRANCH)/$(GIT_COMMIT)"; \ + EXISTING=$$$$( find "$$$$PROF_BASE" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ' ); \ + RUN_INDEX=$$$$((EXISTING + 1)); \ + PROF_RESULTS_PATH="$$$$PROF_BASE/$$$$RUN_INDEX"; \ + mkdir -p "$$$$PROF_RESULTS_PATH" && \ + echo "PROF_RESULTS_PATH: $$$$PROF_RESULTS_PATH" && \ + cd $(TEST_MULTI_SERVER_FRAMEWORK_DIR) && . .venv/bin/activate && \ + DATA_PATH="${4}" PROF_RESULTS_PATH="$$$$PROF_RESULTS_PATH" \ + python3 -m src.multi_server_test $(TEST_MULTI_SERVER_FLAGS) --project-name "${1}-${2}" --compose "${4}/environment.yml" --test "${4}/template.yml" --use-files --listener-dir "${4}/listener" --log-dir "${4}/logs" --output "${4}/logs/result.log" \ + > "${4}/logs/stdout.log" 2> "${4}/logs/stderr.log" || \ + { \ + echo "FAILED: test.multi-server.${1}.${2}"; \ + for f in ${4}/logs/* ${4}/listener/*; do \ + [ -f "$$$$f" ] || continue; \ + echo ""; \ + echo "=== $$$$f ==="; \ + case "$$$$f" in \ + */listener/*) \ + echo "-- line-type counts --"; \ + awk '{print $$$$1}' "$$$$f" | sort | uniq -c; \ + echo "-- last 200 lines --"; \ + ;; \ + esac; \ + tail -200 "$$$$f"; \ + done; \ + exit 1; \ + } +endef + # # TEST_MULTI_SERVER - define all test instances for a suite. # # Discovers *.yml param files in the suite directory and generates # render + test targets for each. # -# ${1} = suite dir name (e.g., 5hs-autoaccept) +# ${1} = suite dir name (e.g. proxy-accept) # define TEST_MULTI_SERVER TEST_MULTI_SERVER_PARAM_FILES.${1} := $$(wildcard $$(DIR)/tests/${1}/*.test.yml) TEST_MULTI_SERVER_TESTS.${1} := $$(foreach p,$$(TEST_MULTI_SERVER_PARAM_FILES.${1}),test.multi-server.${1}.$$(subst .,_,$$(patsubst %.test.yml,%,$$(notdir $$p)))) -$$(foreach p,$$(TEST_MULTI_SERVER_PARAM_FILES.${1}),$$(eval $$(call TEST_MULTI_SERVER_INSTANCE,${1},$$(subst .,_,$$(patsubst %.test.yml,%,$$(notdir $$p))),$$p,$(OUTPUT)/${1}/$$(subst .,_,$$(patsubst %.test.yml,%,$$(notdir $$p)))))) +$$(foreach p,$$(TEST_MULTI_SERVER_PARAM_FILES.${1}),$$(eval $$(call $(if $(filter prof-%,${1}),TEST_MULTI_SERVER_PROF_INSTANCE,TEST_MULTI_SERVER_INSTANCE),${1},$$(subst .,_,$$(patsubst %.test.yml,%,$$(notdir $$p))),$$p,$(OUTPUT)/${1}/$$(subst .,_,$$(patsubst %.test.yml,%,$$(notdir $$p)))))) endef ###################################################################### @@ -195,6 +259,8 @@ $(foreach s,$(TEST_MULTI_SERVER_SUITES),$(eval $(call TEST_MULTI_SERVER,$s))) TEST_MULTI_SERVER_ALL_TESTS := $(foreach s,$(TEST_MULTI_SERVER_SUITES),$(TEST_MULTI_SERVER_TESTS.$(s))) +TEST_MULTI_SERVER_PROF_TESTS := $(foreach s,$(filter prof-%,$(TEST_MULTI_SERVER_SUITES)),$(TEST_MULTI_SERVER_TESTS.$(s))) + ###################################################################### # # Top-level targets @@ -215,6 +281,70 @@ TEST_MULTI_SERVER_CI_TESTS := $(filter %_ci,$(TEST_MULTI_SERVER_ALL_TESTS)) .PHONY: test.multi-server.ci test.multi-server.ci: $(TEST_MULTI_SERVER_CI_TESTS) +# +# Ensure the freeradius-prof image is present before running +# any of the profiling tests. +# + + +# Crossbuild image +FREERADIUS_CROSSBUILD_IMAGE := freeradius40x-build/ubuntu24:latest +# Base profiling image, FreeRADIUS not built on this image +FREERADIUS_PROF_IMAGE := freeradius4-$(PROFILE)/ubuntu24:latest +# Multi-server profiling image; FreeRADIUS dev build specifically for profiling +FREERADIUS_RADENV_PROF_IMAGE := freeradius-prof:latest + +.PHONY: freeradius-prof.image +freeradius-prof.image: + ${Q}if [ -n "$(FORCE_IMAGE_REBUILD)" ]; then \ + $(MAKE) -C $(top_srcdir) crossbuild.ubuntu24.profile.regen; \ + $(MAKE) -C $(top_srcdir) crossbuild.ubuntu24.profile.build; \ + ./src/tests/multi-server/scripts/docker/build/build_image.sh $(if $(BUILD_PLATFORM),BUILD_PLATFORM=$(BUILD_PLATFORM)); \ + elif [ -z "$$(docker images -q $(FREERADIUS_PROF_IMAGE) 2>/dev/null)" ]; then \ + $(MAKE) -C $(top_srcdir) crossbuild.ubuntu24.profile.regen; \ + $(MAKE) -C $(top_srcdir) crossbuild.ubuntu24.profile.build; \ + ./src/tests/multi-server/scripts/docker/build/build_image.sh $(if $(BUILD_PLATFORM),BUILD_PLATFORM=$(BUILD_PLATFORM)); \ + elif [ -z "$$(docker images -q $(FREERADIUS_RADENV_PROF_IMAGE) 2>/dev/null)" ]; then \ + ./src/tests/multi-server/scripts/docker/build/build_image.sh $(if $(BUILD_PLATFORM),BUILD_PLATFORM=$(BUILD_PLATFORM)); \ + else \ + echo "$(FREERADIUS_PROF_IMAGE) and $(FREERADIUS_RADENV_PROF_IMAGE) available, skipping image creation"; \ + fi + +$(TEST_MULTI_SERVER_PROF_TESTS): freeradius-prof.image + +# +# Copy the valgrind profiling helper script into each prof test's output dir +# so it is available alongside the rendered test configs. +# +PROFILING_SCRIPT_SRC := $(DIR)/scripts/profiling/start_valgrind_profiling.sh + +define TEST_MULTI_SERVER_PROF_SCRIPT +$(OUTPUT)/${1}/${2}/start_valgrind_profiling.sh: $(PROFILING_SCRIPT_SRC) + $${Q}mkdir -p $$(@D) + $${Q}cp $$< $$@ + +test.multi-server.${1}.${2}: $(OUTPUT)/${1}/${2}/start_valgrind_profiling.sh +endef + +$(foreach s,$(filter prof-%,$(TEST_MULTI_SERVER_SUITES)),$(foreach p,$(TEST_MULTI_SERVER_PARAM_FILES.$(s)),$(eval $(call TEST_MULTI_SERVER_PROF_SCRIPT,$(s),$(subst .,_,$(patsubst %.test.yml,%,$(notdir $(p)))))))) + +# +# Ensure the ldap image is present before running prof-ldap tests. +# Builds it automatically via docker.openldap.prof if not found. +# +OPENLDAP_PROF_IMAGE := freeradius4/openldap-prof:latest + +.PHONY: openldap.image +openldap.image: + ${Q}if [ -n "$(FORCE_IMAGE_REBUILD)" ] || [ -z "$$(docker images -q $(OPENLDAP_PROF_IMAGE) 2>/dev/null)" ]; then \ + $(MAKE) -C $(top_srcdir) docker.openldap.prof; \ + else \ + echo "$(OPENLDAP_PROF_IMAGE) available, skipping image creation"; \ + fi + ${Q}docker tag $(OPENLDAP_PROF_IMAGE) openldap:latest + +$(TEST_MULTI_SERVER_TESTS.prof-ldap): openldap.image + .PHONY: clean.test.multi-server clean.test.multi-server: ${Q}rm -rf $(OUTPUT) diff --git a/src/tests/multi-server/configs/freeradius/common/mods-available/ldap b/src/tests/multi-server/configs/freeradius/common/mods-available/ldap new file mode 100644 index 00000000000..7dd2725c5d0 --- /dev/null +++ b/src/tests/multi-server/configs/freeradius/common/mods-available/ldap @@ -0,0 +1,30 @@ +ldap { + server = 'openldap' + identity = 'cn=admin,dc=example,dc=com' + password = 'adminpassword' + base_dn = 'dc=example,dc=com' + + update { + control.Password.With-Header += 'userPassword' + } + + user { + base_dn = "${..base_dn}" + filter = "(uid=%{Stripped-User-Name || User-Name})" + } + + options { + res_timeout = 10 + srv_timelimit = 3 + net_timeout = 10 + } + + pool { + start = 0 + min = 1 + max = 5 + connecting = 2 + uses = 0 + lifetime = 0 + } +} diff --git a/src/tests/multi-server/configs/freeradius/common/mods-available/pap b/src/tests/multi-server/configs/freeradius/common/mods-available/pap new file mode 100644 index 00000000000..5c6cde5bb02 --- /dev/null +++ b/src/tests/multi-server/configs/freeradius/common/mods-available/pap @@ -0,0 +1,4 @@ + +pap { + password_attribute = User-Password +} diff --git a/src/tests/multi-server/configs/freeradius/common/mods-available/sql b/src/tests/multi-server/configs/freeradius/common/mods-available/sql new file mode 100644 index 00000000000..9c293755c24 --- /dev/null +++ b/src/tests/multi-server/configs/freeradius/common/mods-available/sql @@ -0,0 +1,35 @@ +sql { + dialect = "mysql" + driver = "${dialect}" + + $-INCLUDE ${modconfdir}/sql/driver/${driver} + + server = "mariadb" + port = 3306 + login = "radius" + password = "radpass" + + radius_db = "radius" + + acct_table1 = "radacct" + acct_table2 = "radacct" + postauth_table = "radpostauth" + authcheck_table = "radcheck" + groupcheck_table = "radgroupcheck" + authreply_table = "radreply" + groupreply_table = "radgroupreply" + usergroup_table = "radusergroup" + + pool { + start = 0 + min = 1 + max = 32 + connecting = 2 + uses = 0 + lifetime = 0 + } + + group_attribute = "${.:instance}-Group" + + $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf +} diff --git a/src/tests/multi-server/configs/freeradius/profiling-ldap/load-generator-packets/packet.conf b/src/tests/multi-server/configs/freeradius/profiling-ldap/load-generator-packets/packet.conf new file mode 100644 index 00000000000..46c2a8fd73c --- /dev/null +++ b/src/tests/multi-server/configs/freeradius/profiling-ldap/load-generator-packets/packet.conf @@ -0,0 +1,3 @@ +User-Name = "testuser" +User-Password = "testpass" +Calling-Station-ID = "F1-F2-F3-F4-F5-F6" diff --git a/src/tests/multi-server/configs/freeradius/profiling-ldap/mods-config/files/authorize b/src/tests/multi-server/configs/freeradius/profiling-ldap/mods-config/files/authorize new file mode 100644 index 00000000000..eba3cb96136 --- /dev/null +++ b/src/tests/multi-server/configs/freeradius/profiling-ldap/mods-config/files/authorize @@ -0,0 +1 @@ +testuser Password.Cleartext := "testpass" diff --git a/src/tests/multi-server/configs/freeradius/profiling-ldap/proto_load_config.env b/src/tests/multi-server/configs/freeradius/profiling-ldap/proto_load_config.env new file mode 100644 index 00000000000..f97e41e7623 --- /dev/null +++ b/src/tests/multi-server/configs/freeradius/profiling-ldap/proto_load_config.env @@ -0,0 +1,14 @@ +export TEST_LOADGEN_START_PPS="100" +export TEST_LOADGEN_MAX_PPS="100" +export TEST_LOADGEN_DURATION="60" +export TEST_LOADGEN_STEP="100" +export TEST_LOADGEN_PARALLEL="1" +export TEST_LOADGEN_MAX_BACKLOG="1000" +export TEST_LOADGEN_REPEAT="no" +export TEST_LOADGEN_NUM_MESSAGES=0 + +for ((pps=$TEST_LOADGEN_START_PPS; pps<=$TEST_LOADGEN_MAX_PPS; pps+=$TEST_LOADGEN_STEP)); do + TEST_LOADGEN_NUM_MESSAGES=$((TEST_LOADGEN_NUM_MESSAGES + TEST_LOADGEN_DURATION * pps)) +done + +export TEST_LOADGEN_NUM_MESSAGES diff --git a/src/tests/multi-server/configs/freeradius/profiling-ldap/radiusd.conf.j2 b/src/tests/multi-server/configs/freeradius/profiling-ldap/radiusd.conf.j2 new file mode 100644 index 00000000000..f699156893a --- /dev/null +++ b/src/tests/multi-server/configs/freeradius/profiling-ldap/radiusd.conf.j2 @@ -0,0 +1,139 @@ +name = profiling-server + +raddbdir = /etc/freeradius +confdir = ${raddbdir} +modconfdir = ${confdir}/mods-config +logdir = /var/log/freeradius +radacctdir = ${logdir}/radacct + +security { + allow_core_dumps = yes +} + +modules { + + # Common ldap module configuration + {% include "freeradius/common/mods-available/ldap" %} + + # Common pap module configuration + {% include "freeradius/common/mods-available/pap" %} + + # Common always module configuration, required for the control policy + {% include "freeradius/common/mods-available/always" %} + +} + +policy { + + # Common control policy configuration, needed for accept action + {% include "freeradius/common/policy.d/control" %} + +} + +server profiling-server { + + namespace = radius + + listen load { + handler = load + type = Access-Request + transport = step + + step { + + # Default packet config to use by proto_load module + filename = ${confdir}/load-generator-packets/packet.conf + + # Saving proto_load statistics disabled by default, can be enabled for debugging purposes. + csv = ${confdir}/stats/load-generator-stats.csv + + max_attributes = 64 + + # + # The load profile is configured via environment variables set + # in the testcase configuration files. + # + start_pps = $ENV{TEST_LOADGEN_START_PPS} + max_pps = $ENV{TEST_LOADGEN_MAX_PPS} + duration = $ENV{TEST_LOADGEN_DURATION} + step = $ENV{TEST_LOADGEN_STEP} + max_backlog = $ENV{TEST_LOADGEN_MAX_BACKLOG} + parallel = $ENV{TEST_LOADGEN_PARALLEL} + num_messages = $ENV{TEST_LOADGEN_NUM_MESSAGES} + repeat = no + } + } + + listen authentication { + type = Access-Request + transport = udp + require_message_authenticator = auto + limit_proxy_state = auto + + limit { + max_clients = 256 + max_connections = 256 + idle_timeout = 60.0 + dynamic_timeout = 600.0 + nak_lifetime = 30.0 + cleanup_delay = 5.0 + } + + udp { + ipaddr = * + port = 1812 + networks { + allow = 127/8 + allow = 192.0.2/24 + } + } + + tcp { + ipaddr = * + port = 1812 + networks { + allow = 127/8 + allow = 192.0.2/24 + } + } + } + + listen authentication { + type = Access-Request + transport = tcp + + tcp { + ipaddr = * + port = 1812 + networks { + allow = 127/8 + allow = 192.0.2/24 + } + } + } + + client localhost { + shortname = client-localhost + ipaddr = * + secret = testing123 + } + + recv Access-Request { + ldap + # pap module always listed last + pap + } + + authenticate pap { + pap + } + + send Access-Accept { + + } + + send Access-Reject { + + } + +} diff --git a/src/tests/multi-server/configs/freeradius/profiling-ldap/stats/load-generator-stats.csv b/src/tests/multi-server/configs/freeradius/profiling-ldap/stats/load-generator-stats.csv new file mode 100644 index 00000000000..34c1880c5db --- /dev/null +++ b/src/tests/multi-server/configs/freeradius/profiling-ldap/stats/load-generator-stats.csv @@ -0,0 +1 @@ +"time","last_packet","rtt","rttvar","pps","pps_accepted","sent","received","backlog","max_backlog"," /dev/null 2>&1"] + interval: 2s + timeout: 5s + retries: 10 + start_period: 15s + <<: *id001 + profiling-server: + image: freeradius-prof:latest + depends_on: + openldap: + condition: service_healthy + volumes: + # profiling-server server config + - ${DATA_PATH}/freeradius/profiling-ldap/radiusd.conf:/etc/freeradius/radiusd.conf + # files module configuration + - ${DATA_PATH}/freeradius/profiling-ldap/mods-config/files/authorize:/etc/freeradius/mods-config/files/authorize + # proto_load packet configuration and statistics output + - ${DATA_PATH}/freeradius/profiling-ldap/proto_load_config.env:/etc/freeradius/proto_load_config.env + - ${DATA_PATH}/freeradius/profiling-ldap/load-generator-packets/:/etc/freeradius/load-generator-packets/ + - ${DATA_PATH}/freeradius/profiling-ldap/stats/load-generator-stats.csv:/etc/freeradius/stats/load-generator-stats.csv + # Profiling scripts + - ${DATA_PATH}/start_valgrind_profiling.sh:/etc/freeradius/start_valgrind_profiling.sh + # Profiling results directory + - ${PROF_RESULTS_PATH}/:/etc/prof-results/ + # Listener directory + - ${LISTENER_DIR}/:/var/run/multi-server/ + entrypoint: + - bash + - -lc + - | + # Keep the container alive. The test framework starts FreeRADIUS + # and runs commands via 'docker exec' so it can control timing. + # + # Start the server after configuring environment variables from test case's template.yml.j2 file. + sleep infinity + <<: *id001 + diff --git a/src/tests/multi-server/environments/profiling-mysql.yml.j2 b/src/tests/multi-server/environments/profiling-mysql.yml.j2 new file mode 100644 index 00000000000..ee5195655f1 --- /dev/null +++ b/src/tests/multi-server/environments/profiling-mysql.yml.j2 @@ -0,0 +1,63 @@ +# --------------------------------------------------------------- +# Docker Compose Test Environment: +# +# A single server instance for profiling +# +# --------------------------------------------------------------- +x-common-config: &id001 + cap_add: + - NET_ADMIN + - SYS_PTRACE + environment: + TEST_PROJECT_NAME: ${COMPOSE_PROJECT_NAME} + TEST_SUBNET: {{ test_subnet | default('172.16.0.0/12') }} +services: + mariadb: + image: mariadb:10.5 + environment: + MYSQL_ROOT_PASSWORD: rootpass + MYSQL_DATABASE: radius + MYSQL_USER: radius + MYSQL_PASSWORD: radpass + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p${MYSQL_ROOT_PASSWORD} > /dev/null 2>&1"] + interval: 2s + timeout: 5s + retries: 10 + start_period: 15s + volumes: + - /tmp/${COMPOSE_PROJECT_NAME}-mariadb-data:/var/lib/mysql + - ${DATA_PATH}/mariadb/default/init.sql:/docker-entrypoint-initdb.d/init.sql + <<: *id001 + profiling-server: + image: freeradius-prof:latest + depends_on: + mariadb: + condition: service_healthy + volumes: + # profiling-server server config + - ${DATA_PATH}/freeradius/profiling-mysql/radiusd.conf:/etc/freeradius/radiusd.conf + # files module configuration + - ${DATA_PATH}/freeradius/profiling-mysql/mods-config/files/authorize:/etc/freeradius/mods-config/files/authorize + # proto_load packet configuration and statistics output + - ${DATA_PATH}/freeradius/profiling-mysql/proto_load_config.env:/etc/freeradius/proto_load_config.env + - ${DATA_PATH}/freeradius/profiling-mysql/load-generator-packets/:/etc/freeradius/load-generator-packets/ + - ${DATA_PATH}/freeradius/profiling-mysql/stats/load-generator-stats.csv:/etc/freeradius/stats/load-generator-stats.csv + # Profiling scripts + - ${DATA_PATH}/start_valgrind_profiling.sh:/etc/freeradius/start_valgrind_profiling.sh + # Profiling results directory + - ${PROF_RESULTS_PATH}/:/etc/prof-results/ + # Listener directory + - ${LISTENER_DIR}/:/var/run/multi-server/ + entrypoint: + - bash + - -lc + - | + # Keep the container alive. The test framework starts FreeRADIUS + # and runs commands via 'docker exec' so it can control timing. + # + # Start the server after configuring environment variables from test case's template.yml.j2 file. + sleep infinity + <<: *id001 + diff --git a/src/tests/multi-server/environments/profiling-pap-auth.yml.j2 b/src/tests/multi-server/environments/profiling-pap-auth.yml.j2 new file mode 100644 index 00000000000..3f7832f7294 --- /dev/null +++ b/src/tests/multi-server/environments/profiling-pap-auth.yml.j2 @@ -0,0 +1,41 @@ +# --------------------------------------------------------------- +# Docker Compose Test Environment: +# +# A single server instance for profiling +# +# --------------------------------------------------------------- +x-common-config: &id001 + cap_add: + - NET_ADMIN + - SYS_PTRACE + environment: + TEST_PROJECT_NAME: ${COMPOSE_PROJECT_NAME} + TEST_SUBNET: {{ test_subnet | default('172.16.0.0/12') }} +services: + profiling-server: + image: freeradius-prof:latest + volumes: + # profiling-server server config + - ${DATA_PATH}/freeradius/profiling-server-pap-auth/radiusd.conf:/etc/freeradius/radiusd.conf + # files module configuration + - ${DATA_PATH}/freeradius/profiling-server-pap-auth/mods-config/files/authorize:/etc/freeradius/mods-config/files/authorize + # proto_load packet configuration and statistics output + - ${DATA_PATH}/freeradius/profiling-server-pap-auth/proto_load_config.env:/etc/freeradius/proto_load_config.env + - ${DATA_PATH}/freeradius/profiling-server-pap-auth/load-generator-packets/:/etc/freeradius/load-generator-packets/ + - ${DATA_PATH}/freeradius/profiling-server-pap-auth/stats/load-generator-stats.csv:/etc/freeradius/stats/load-generator-stats.csv + # Profiling scripts + - ${DATA_PATH}/start_valgrind_profiling.sh:/etc/freeradius/start_valgrind_profiling.sh + # Profiling results directory + - ${PROF_RESULTS_PATH}/:/etc/prof-results/ + # Listener directory + - ${LISTENER_DIR}/:/var/run/multi-server/ + entrypoint: + - bash + - -lc + - | + # Keep the container alive. The test framework starts FreeRADIUS + # and runs commands via 'docker exec' so it can control timing. + # + # Start the server after configuring environment variables from test case's template.yml.j2 file. + sleep infinity + <<: *id001 diff --git a/src/tests/multi-server/environments/profiling.yml.j2 b/src/tests/multi-server/environments/profiling.yml.j2 new file mode 100644 index 00000000000..fdf7be50b77 --- /dev/null +++ b/src/tests/multi-server/environments/profiling.yml.j2 @@ -0,0 +1,39 @@ +# --------------------------------------------------------------- +# Docker Compose Test Environment: +# +# A single server instance for profiling +# +# --------------------------------------------------------------- +x-common-config: &id001 + cap_add: + - NET_ADMIN + - SYS_PTRACE + environment: + TEST_PROJECT_NAME: ${COMPOSE_PROJECT_NAME} + TEST_SUBNET: {{ test_subnet | default('172.16.0.0/12') }} +services: + profiling-server: + image: freeradius-prof:latest + volumes: + # profiling-server server config + - ${DATA_PATH}/freeradius/profiling-server/radiusd.conf:/etc/freeradius/radiusd.conf + # proto_load packet configuration and statistics output + - ${DATA_PATH}/freeradius/profiling-server/proto_load_config.env:/etc/freeradius/proto_load_config.env + - ${DATA_PATH}/freeradius/profiling-server/load-generator-packets/:/etc/freeradius/load-generator-packets/ + - ${DATA_PATH}/freeradius/profiling-server/stats/load-generator-stats.csv:/etc/freeradius/stats/load-generator-stats.csv + # Profiling scripts + - ${DATA_PATH}/start_valgrind_profiling.sh:/etc/freeradius/start_valgrind_profiling.sh + # Profiling results directory + - ${PROF_RESULTS_PATH}/:/etc/prof-results/ + # Listener directory + - ${LISTENER_DIR}/:/var/run/multi-server/ + entrypoint: + - bash + - -lc + - | + # Keep the container alive. The test framework starts FreeRADIUS + # and runs commands via 'docker exec' so it can control timing. + # + # Start the server after configuring environment variables from test case's template.yml.j2 file. + sleep infinity + <<: *id001 diff --git a/src/tests/multi-server/scripts/docker/build/Dockerfile.multi-server-prof b/src/tests/multi-server/scripts/docker/build/Dockerfile.multi-server-prof new file mode 100644 index 00000000000..76448ac91b0 --- /dev/null +++ b/src/tests/multi-server/scripts/docker/build/Dockerfile.multi-server-prof @@ -0,0 +1,36 @@ +# Dockerfile for multi-server profiling tests. +# Builds off of "base" profiling image, configures FreeRADIUS and builds it. +FROM freeradius4-default-profiling/ubuntu24:latest + +# CFLAGS used for profiling build: +# -g3 Maximum debug info for callgrind symbol resolution +# -O1 Basic optimisation for realistic hotspot costs; inlining/vectorisation/ +# unrolling disabled below so the call graph matches the source. +# -fno-omit-frame-pointer Keep frame pointers for callgrind stack walking +# -fno-inline Preserve call edges (suppresses CC_HINT(flatten) too) +# -Dalways_inline= Strip always_inline, which -fno-inline does not suppress +# -fno-plt Resolve cross-library calls via GOT rather than PLT stubs; +# PLT stubs have no DWARF info and show as ??? in callgrind. +# -fno-builtin Keep stdlib calls (memcpy, strlen, etc.) visible in the graph +# -fno-optimize-sibling-calls Suppress tail-call elimination (-O1 can still apply it) +RUN ./configure \ + --enable-developer \ + --disable-verify-ptr \ + --with-raddbdir=/etc/freeradius \ + CFLAGS="-g3 -O1 -fno-omit-frame-pointer -fno-inline -Dalways_inline= -fno-plt -fno-builtin -fno-optimize-sibling-calls" \ + LDFLAGS="-fno-omit-frame-pointer" + +RUN make + +# FreeRADIUS installed in /etc/freeradius +RUN make install + +# Setup softlinks to be able to run server with `freeradius` cmd +RUN ln -sf /usr/local/sbin/radiusd /usr/local/sbin/freeradius && \ + ln -sf /etc/freeradius/radiusd.conf /etc/freeradius/freeradius.conf && \ + ln -sf /etc/freeradius /etc/raddb + +# Generate the self-signed RSA (and DH/EC) certificates for testing. +RUN cd /etc/freeradius/certs && make + + diff --git a/src/tests/multi-server/scripts/docker/build/build_image.sh b/src/tests/multi-server/scripts/docker/build/build_image.sh new file mode 100755 index 00000000000..e77889e80d6 --- /dev/null +++ b/src/tests/multi-server/scripts/docker/build/build_image.sh @@ -0,0 +1,15 @@ +#!/bin/bash +for arg in "$@"; do + case $arg in + BUILD_PLATFORM=*) BUILD_PLATFORM="${arg#*=}" ;; + esac +done + +# This allows us to build an image on Apple Silicon where the base image was built on an linux/amd64 platform. +# Example usage: BUILD_PLATFORM=linux/amd64 ./build_image.sh +PLATFORM_ARG="" +if [ -n "${BUILD_PLATFORM}" ]; then + PLATFORM_ARG="--platform=${BUILD_PLATFORM}" +fi + +docker build ${PLATFORM_ARG} -f src/tests/multi-server/scripts/docker/build/Dockerfile.multi-server-prof -t freeradius-prof:latest . diff --git a/src/tests/multi-server/scripts/docker/build/run_container.sh b/src/tests/multi-server/scripts/docker/build/run_container.sh new file mode 100755 index 00000000000..35c0b42136d --- /dev/null +++ b/src/tests/multi-server/scripts/docker/build/run_container.sh @@ -0,0 +1,15 @@ +#!/bin/bash +for arg in "$@"; do + case $arg in + BUILD_PLATFORM=*) BUILD_PLATFORM="${arg#*=}" ;; + esac +done + +# This allows us to run a container on Apple Silicon where the base image was built on an linux/amd64 platform. +# Example usage: BUILD_PLATFORM=linux/amd64 ./run_container.sh +PLATFORM_ARG="" +if [ -n "${BUILD_PLATFORM}" ]; then + PLATFORM_ARG="--platform=${BUILD_PLATFORM}" +fi + +docker run -it --rm ${PLATFORM_ARG} -v "$(pwd)/prof-results:/etc/prof-results" --name freeradius-radenv-container freeradius-prof:latest diff --git a/src/tests/multi-server/scripts/profiling/README.md b/src/tests/multi-server/scripts/profiling/README.md new file mode 100644 index 00000000000..cd58387b2bf --- /dev/null +++ b/src/tests/multi-server/scripts/profiling/README.md @@ -0,0 +1,30 @@ +## generate_callgrind_report.py + +python3 src/tests/multi-server/scripts/generate_callgrind_report.py \ + \ + --title "FreeRADIUS prof-accept 5min" \ + --text-output valgrind_report_radenv_prof_accept.txt \ + --md-output valgrind_report_radenv_prof_accept.md + +## Generate text based report from Valgrind/Callgrind results +callgrind_annotate $(find . -name "callgrind.out.*" -size +0c | sort) > callgrind_report.txt + +## Generate SVG sharable file of valgrind/callgrind results + +Dependency: ```brew install gprof2dot``` + +Generate SVG file for one worker thread: +``` +gprof2dot --format=callgrind \ + /callgrind.out.1004-04 \ + | dot -Tsvg -o callgraph_thread04.svg +``` + +Generate SVG file per worker thread: +``` +for f in /callgrind.out.1004-{04..12}; do + thread=$(grep "^thread:" "$f" | awk '{print $2}') + gprof2dot --format=callgrind "$f" \ + | dot -Tsvg -o "callgraph_thread${thread}.svg" +done +``` diff --git a/src/tests/multi-server/scripts/profiling/generate_callgrind_report.py b/src/tests/multi-server/scripts/profiling/generate_callgrind_report.py new file mode 100755 index 00000000000..eef0bcac8d3 --- /dev/null +++ b/src/tests/multi-server/scripts/profiling/generate_callgrind_report.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""Generate text and markdown profiling reports from callgrind output files.""" + +import argparse +import os +import re +import subprocess +import sys +from collections import defaultdict + + +def parse_thread_number(callgrind_file): + with open(callgrind_file) as f: + for line in f: + if line.startswith('thread:'): + return int(line.split()[1]) + return 0 + + +def parse_summary_ir(callgrind_file): + with open(callgrind_file) as f: + for line in f: + if line.startswith('summary:'): + return int(line.split()[1]) + return 0 + + +def run_callgrind_annotate(callgrind_file): + result = subprocess.run( + ['callgrind_annotate', '--auto=no', '--threshold=100', callgrind_file], + capture_output=True, text=True + ) + return result.stdout + + +def parse_module_entries(annotate_output): + """Extract rlm_* and proto_load function rows from callgrind_annotate output.""" + entries = [] + for line in annotate_output.splitlines(): + if 'rlm_' not in line and 'proto_load' not in line: + continue + if not line.strip() or line.startswith('-') or line.startswith('='): + continue + + ir_match = re.match(r'^\s*([\d,]+)\s+\(\s*([\d.]+)%\)', line) + if not ir_match: + continue + + func_match = re.search(r'([^/\s]+):(\w+)\s+\[([^\]]+)\]', line) + if not func_match: + continue + + entries.append({ + 'ir': int(ir_match.group(1).replace(',', '')), + 'ir_pct': float(ir_match.group(2)), + 'function': func_match.group(2), + 'lib': os.path.basename(func_match.group(3)), + }) + return entries + + +def fmt_ir(n): + return f"{n:,}" + + +def generate_markdown(results_dir, thread_data, title): + lines = [] + lines.append(f"# {title}") + lines.append("") + lines.append(f"**Results:** `{results_dir}`") + lines.append("") + + # Collect all unique function+lib pairs + lib_to_funcs = defaultdict(set) + for td in thread_data.values(): + for e in td['entries']: + lib_to_funcs[e['lib']].add(e['function']) + + lines.append("## Functions Found") + lines.append("") + lines.append("| Function | Library |") + lines.append("|---|---|") + for lib in sorted(lib_to_funcs): + funcs = ', '.join(f'`{f}`' for f in sorted(lib_to_funcs[lib])) + lines.append(f"| {funcs} | `{lib}` |") + lines.append("") + + lines.append("## CPU Share (Ir = Instructions Retired)") + lines.append("") + + for thread_num, td in sorted(thread_data.items()): + if not td['entries']: + continue + + total_ir = td['total_ir'] + lines.append(f"### Thread {thread_num:02d} — Total: {fmt_ir(total_ir)} Ir") + lines.append("") + lines.append("| Function | Library | Ir | % of Thread |") + lines.append("|---|---|---|---|") + + module_total = 0 + for e in sorted(td['entries'], key=lambda x: -x['ir']): + lines.append(f"| `{e['function']}` | `{e['lib']}` | {fmt_ir(e['ir'])} | {e['ir_pct']:.2f}% |") + module_total += e['ir'] + + module_pct = (module_total / total_ir * 100) if total_ir else 0 + lines.append(f"| **Total** | | **{fmt_ir(module_total)}** | **{module_pct:.2f}%** |") + lines.append("") + + all_module_ir = sum(e['ir'] for td in thread_data.values() for e in td['entries']) + all_total_ir = sum(td['total_ir'] for td in thread_data.values()) + overall_pct = (all_module_ir / all_total_ir * 100) if all_total_ir else 0 + + lines.append("## Takeaway") + lines.append("") + lines.append( + f"`rlm_*` and `proto_load` combined account for **{overall_pct:.2f}% of total instructions** " + f"across all threads ({fmt_ir(all_module_ir)} of {fmt_ir(all_total_ir)} Ir total)." + ) + lines.append("") + + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser(description="Generate profiling reports from callgrind results") + parser.add_argument("results_dir", help="Directory containing callgrind.out.* files") + parser.add_argument("--title", default=None, help="Report title") + parser.add_argument("--text-output", default=None, help="Path for combined callgrind_annotate text report") + parser.add_argument("--md-output", default=None, help="Path for markdown summary report") + args = parser.parse_args() + + results_dir = args.results_dir + if not os.path.isdir(results_dir): + print(f"error: {results_dir} is not a directory", file=sys.stderr) + sys.exit(1) + + files = sorted([ + os.path.join(results_dir, f) + for f in os.listdir(results_dir) + if re.match(r'callgrind\.out\.\d+(-\d+)?$', f) and os.path.getsize(os.path.join(results_dir, f)) > 0 + ]) + + if not files: + print(f"error: no callgrind.out.* files found in {results_dir}", file=sys.stderr) + sys.exit(1) + + title = args.title or f"FreeRADIUS Callgrind Profile: {os.path.basename(os.path.normpath(results_dir))}" + + thread_data = {} + text_sections = [] + + for f in files: + thread_num = parse_thread_number(f) + total_ir = parse_summary_ir(f) + print(f" {os.path.basename(f)}: thread {thread_num:02d}, {total_ir:,} Ir", file=sys.stderr) + + annotate_output = run_callgrind_annotate(f) + text_sections.append(f"{'='*80}\n{os.path.basename(f)} (thread {thread_num:02d})\n{'='*80}\n{annotate_output}") + + if thread_num not in thread_data: + thread_data[thread_num] = {'total_ir': 0, 'entries': []} + thread_data[thread_num]['total_ir'] += total_ir + thread_data[thread_num]['entries'].extend(parse_module_entries(annotate_output)) + + if args.text_output: + with open(args.text_output, 'w') as out: + out.write("\n\n".join(text_sections)) + print(f"text report -> {args.text_output}", file=sys.stderr) + + md = generate_markdown(results_dir, thread_data, title) + + if args.md_output: + with open(args.md_output, 'w') as out: + out.write(md) + print(f"markdown report -> {args.md_output}", file=sys.stderr) + else: + print(md) + + +if __name__ == '__main__': + main() diff --git a/src/tests/multi-server/scripts/profiling/start_valgrind_profiling.sh b/src/tests/multi-server/scripts/profiling/start_valgrind_profiling.sh new file mode 100755 index 00000000000..2a69c917e87 --- /dev/null +++ b/src/tests/multi-server/scripts/profiling/start_valgrind_profiling.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# To be run inside the profiling container + +# Clear any stale marker from a previous run +rm -f /etc/prof-results/.profiling_complete +rm -f /etc/prof-results/valgrind_profiling.log + +exec > /etc/prof-results/valgrind_profiling.log 2>&1 + +# Ignore SIGTERM — freeradius broadcasts it to the process group on shutdown, +# which would otherwise kill this script before it can touch .profiling_complete +trap '' SIGTERM + +# Load proto_load config to get packet settings +source /etc/freeradius/proto_load_config.env + +# Calculate approximate send duration +SEND_DURATION=$(( TEST_LOADGEN_NUM_MESSAGES / TEST_LOADGEN_START_PPS )) +PROFILE_DURATION_BUFFER=15 +PROFILE_DURATION=$SEND_DURATION-$PROFILE_DURATION_BUFFER + +# Start freeradius under valgrind with instrumentation off +valgrind \ + --tool=callgrind \ + --callgrind-out-file=/etc/prof-results/callgrind.out.%p \ + --trace-children=yes \ + --separate-threads=yes \ + --dump-instr=yes \ + --collect-jumps=yes \ + --cache-sim=yes \ + --branch-sim=yes \ + --keep-debuginfo=yes \ + --instr-atstart=no \ + freeradius -f -l stdout -S resources.talloc_skip_cleanup=yes 2>&1 | \ + tee /etc/prof-results/freeradius.log & +VALGRIND_PID=$! + +# Wait for server ready (bail out if freeradius fails to start under valgrind) +STARTUP_TIMEOUT=300 +STARTUP_ELAPSED=0 +until grep -q "Ready to process requests" /etc/prof-results/freeradius.log; do + sleep 1 + STARTUP_ELAPSED=$(( STARTUP_ELAPSED + 1 )) + if [ ${STARTUP_ELAPSED} -ge ${STARTUP_TIMEOUT} ]; then + echo "ERROR: freeradius did not become ready within ${STARTUP_TIMEOUT}s, aborting" + kill -SIGKILL ${VALGRIND_PID} 2>/dev/null + exit 1 + fi +done + +# Enable instrumentation. callgrind_control auto-detects the running callgrind +# instance and prints "PID : freeradius ..." — capture that to get the PID +# we need later for the graceful shutdown signal. +echo "INFO: enabling callgrind instrumentation" +CTRL_OUT=$(callgrind_control --instr=on) +printf '%s\n' "$CTRL_OUT" +FR_PID=$(printf '%s\n' "$CTRL_OUT" | grep -oP 'PID \K\d+(?=: freeradius)' | head -1) +echo "Freeradius PID: ${FR_PID}" + +# Wait for approximate send duration +sleep ${SEND_DURATION} + +# Stop instrumentation before shutdown so valgrind only flushes already-collected data +echo "INFO: disabling callgrind instrumentation" +CTRL_OUT=$(callgrind_control --instr=off 2>/dev/null || true) +printf '%s\n' "$CTRL_OUT" + +# Graceful shutdown (equivalent to Ctrl+C) +if [ -z "${FR_PID}" ]; then + echo "WARNING: could not determine freeradius PID from callgrind_control output, sending SIGINT to valgrind pipeline instead" + kill -SIGINT ${VALGRIND_PID} 2>/dev/null || true +else + echo "INFO: killing freeradius process ${FR_PID} with SIGINT for graceful shutdown" + kill -SIGINT ${FR_PID} +fi + +# Give valgrind time to write callgrind output after freeradius exits +echo "INFO: sleeping for 5s" +sleep 5 + +# Signal that valgrind has finished writing all profiling data +echo "INFO: Profiling complete at $(date)" + +echo "INFO: running callgrind_annotate to generate report" +#callgrind_annotate $(find /etc/prof-results -name "callgrind.out.*" -size +0c | sort) > /etc/prof-results/callgrind_report.txt +cmd='callgrind_annotate $(find /etc/prof-results -name "callgrind.out.*" -size +0c | sort) > /etc/prof-results/callgrind_report.txt' +echo "$cmd" +eval "$cmd" + +# Restore stdout/stderr +exec > /dev/null 2>&1 diff --git a/src/tests/multi-server/tests/prof-accept/5min.test.yml b/src/tests/multi-server/tests/prof-accept/5min.test.yml new file mode 100644 index 00000000000..494dbd39455 --- /dev/null +++ b/src/tests/multi-server/tests/prof-accept/5min.test.yml @@ -0,0 +1,17 @@ +# Topology - N/A for profiling test + +# Routing - N/A for profiling test + +# Load generator configuration +loadgen: + start_pps: 500 + max_pps: 500 + duration: 300 + step: 500 + parallel: 1 + max_backlog: 1000 + repeat: "no" + +# Test framework +test_timeout: 350 +test_verify_timeout: 340 diff --git a/src/tests/multi-server/tests/prof-accept/environment.yml.j2 b/src/tests/multi-server/tests/prof-accept/environment.yml.j2 new file mode 120000 index 00000000000..1fdf2bcfe4d --- /dev/null +++ b/src/tests/multi-server/tests/prof-accept/environment.yml.j2 @@ -0,0 +1 @@ +../../environments/profiling.yml.j2 \ No newline at end of file diff --git a/src/tests/multi-server/tests/prof-accept/short.ci.test.yml b/src/tests/multi-server/tests/prof-accept/short.ci.test.yml new file mode 100644 index 00000000000..b009c3ed3a8 --- /dev/null +++ b/src/tests/multi-server/tests/prof-accept/short.ci.test.yml @@ -0,0 +1,18 @@ +# Topology - N/A for profiling test + +# Routing - N/A for profiling test + +# Load generator configuration +loadgen: + start_pps: 100 + max_pps: 100 + duration: 60 + step: 100 + parallel: 1 + max_backlog: 1000 + repeat: "no" + +# Test framework +test_timeout: 125 +test_state1_verify_timeout: 120 +test_state2_verify_timeout: 30 diff --git a/src/tests/multi-server/tests/prof-accept/template.yml.j2 b/src/tests/multi-server/tests/prof-accept/template.yml.j2 new file mode 100644 index 00000000000..185bcd8b0a2 --- /dev/null +++ b/src/tests/multi-server/tests/prof-accept/template.yml.j2 @@ -0,0 +1,38 @@ +timeout: {{ test_timeout }} +state_order: sequence +states: + state_1: + description: > + Baseline profiling test + host: + profiling-server: + actions: + - execute_command: + command: | + # + # proto_load configuration via environment variables + # + {%- for key, value in loadgen.items() %} + export TEST_LOADGEN_{{ key | upper }}="{{ value }}" + {%- endfor %} + TEST_LOADGEN_NUM_MESSAGES=0 + for ((pps=$TEST_LOADGEN_START_PPS; pps<=$TEST_LOADGEN_MAX_PPS; pps+=$TEST_LOADGEN_STEP)); do + TEST_LOADGEN_NUM_MESSAGES=$((TEST_LOADGEN_NUM_MESSAGES + TEST_LOADGEN_DURATION * pps)) + done + export TEST_LOADGEN_NUM_MESSAGES + # + # Starting load-generator server which will generate traffic based on env configuration + # from above. + # + printf "Starting load-generator with the following configuration:\n" + {%- for key, value in loadgen.items() %} + printf " {{ key | upper }}:%s\n" "$TEST_LOADGEN_{{ key | upper }}" + {%- endfor %} + printf " NUM_MESSAGES: %s\n" "$TEST_LOADGEN_NUM_MESSAGES" + + source /etc/freeradius/start_valgrind_profiling.sh + + detach: true + verify: + timeout: {{ test_state1_verify_timeout }} + trigger_mode: unordered diff --git a/src/tests/multi-server/tests/prof-ldap/5min.test.yml b/src/tests/multi-server/tests/prof-ldap/5min.test.yml new file mode 100644 index 00000000000..494dbd39455 --- /dev/null +++ b/src/tests/multi-server/tests/prof-ldap/5min.test.yml @@ -0,0 +1,17 @@ +# Topology - N/A for profiling test + +# Routing - N/A for profiling test + +# Load generator configuration +loadgen: + start_pps: 500 + max_pps: 500 + duration: 300 + step: 500 + parallel: 1 + max_backlog: 1000 + repeat: "no" + +# Test framework +test_timeout: 350 +test_verify_timeout: 340 diff --git a/src/tests/multi-server/tests/prof-ldap/environment.yml.j2 b/src/tests/multi-server/tests/prof-ldap/environment.yml.j2 new file mode 120000 index 00000000000..6ffe47ad64f --- /dev/null +++ b/src/tests/multi-server/tests/prof-ldap/environment.yml.j2 @@ -0,0 +1 @@ +../../environments/profiling-ldap.yml.j2 \ No newline at end of file diff --git a/src/tests/multi-server/tests/prof-ldap/short.ci.test.yml b/src/tests/multi-server/tests/prof-ldap/short.ci.test.yml new file mode 100644 index 00000000000..b009c3ed3a8 --- /dev/null +++ b/src/tests/multi-server/tests/prof-ldap/short.ci.test.yml @@ -0,0 +1,18 @@ +# Topology - N/A for profiling test + +# Routing - N/A for profiling test + +# Load generator configuration +loadgen: + start_pps: 100 + max_pps: 100 + duration: 60 + step: 100 + parallel: 1 + max_backlog: 1000 + repeat: "no" + +# Test framework +test_timeout: 125 +test_state1_verify_timeout: 120 +test_state2_verify_timeout: 30 diff --git a/src/tests/multi-server/tests/prof-ldap/template.yml.j2 b/src/tests/multi-server/tests/prof-ldap/template.yml.j2 new file mode 100644 index 00000000000..185bcd8b0a2 --- /dev/null +++ b/src/tests/multi-server/tests/prof-ldap/template.yml.j2 @@ -0,0 +1,38 @@ +timeout: {{ test_timeout }} +state_order: sequence +states: + state_1: + description: > + Baseline profiling test + host: + profiling-server: + actions: + - execute_command: + command: | + # + # proto_load configuration via environment variables + # + {%- for key, value in loadgen.items() %} + export TEST_LOADGEN_{{ key | upper }}="{{ value }}" + {%- endfor %} + TEST_LOADGEN_NUM_MESSAGES=0 + for ((pps=$TEST_LOADGEN_START_PPS; pps<=$TEST_LOADGEN_MAX_PPS; pps+=$TEST_LOADGEN_STEP)); do + TEST_LOADGEN_NUM_MESSAGES=$((TEST_LOADGEN_NUM_MESSAGES + TEST_LOADGEN_DURATION * pps)) + done + export TEST_LOADGEN_NUM_MESSAGES + # + # Starting load-generator server which will generate traffic based on env configuration + # from above. + # + printf "Starting load-generator with the following configuration:\n" + {%- for key, value in loadgen.items() %} + printf " {{ key | upper }}:%s\n" "$TEST_LOADGEN_{{ key | upper }}" + {%- endfor %} + printf " NUM_MESSAGES: %s\n" "$TEST_LOADGEN_NUM_MESSAGES" + + source /etc/freeradius/start_valgrind_profiling.sh + + detach: true + verify: + timeout: {{ test_state1_verify_timeout }} + trigger_mode: unordered diff --git a/src/tests/multi-server/tests/prof-mysql/5min.test.yml b/src/tests/multi-server/tests/prof-mysql/5min.test.yml new file mode 100644 index 00000000000..494dbd39455 --- /dev/null +++ b/src/tests/multi-server/tests/prof-mysql/5min.test.yml @@ -0,0 +1,17 @@ +# Topology - N/A for profiling test + +# Routing - N/A for profiling test + +# Load generator configuration +loadgen: + start_pps: 500 + max_pps: 500 + duration: 300 + step: 500 + parallel: 1 + max_backlog: 1000 + repeat: "no" + +# Test framework +test_timeout: 350 +test_verify_timeout: 340 diff --git a/src/tests/multi-server/tests/prof-mysql/environment.yml.j2 b/src/tests/multi-server/tests/prof-mysql/environment.yml.j2 new file mode 120000 index 00000000000..8b720979493 --- /dev/null +++ b/src/tests/multi-server/tests/prof-mysql/environment.yml.j2 @@ -0,0 +1 @@ +../../environments/profiling-mysql.yml.j2 \ No newline at end of file diff --git a/src/tests/multi-server/tests/prof-mysql/short.ci.test.yml b/src/tests/multi-server/tests/prof-mysql/short.ci.test.yml new file mode 100644 index 00000000000..b009c3ed3a8 --- /dev/null +++ b/src/tests/multi-server/tests/prof-mysql/short.ci.test.yml @@ -0,0 +1,18 @@ +# Topology - N/A for profiling test + +# Routing - N/A for profiling test + +# Load generator configuration +loadgen: + start_pps: 100 + max_pps: 100 + duration: 60 + step: 100 + parallel: 1 + max_backlog: 1000 + repeat: "no" + +# Test framework +test_timeout: 125 +test_state1_verify_timeout: 120 +test_state2_verify_timeout: 30 diff --git a/src/tests/multi-server/tests/prof-mysql/template.yml.j2 b/src/tests/multi-server/tests/prof-mysql/template.yml.j2 new file mode 100644 index 00000000000..185bcd8b0a2 --- /dev/null +++ b/src/tests/multi-server/tests/prof-mysql/template.yml.j2 @@ -0,0 +1,38 @@ +timeout: {{ test_timeout }} +state_order: sequence +states: + state_1: + description: > + Baseline profiling test + host: + profiling-server: + actions: + - execute_command: + command: | + # + # proto_load configuration via environment variables + # + {%- for key, value in loadgen.items() %} + export TEST_LOADGEN_{{ key | upper }}="{{ value }}" + {%- endfor %} + TEST_LOADGEN_NUM_MESSAGES=0 + for ((pps=$TEST_LOADGEN_START_PPS; pps<=$TEST_LOADGEN_MAX_PPS; pps+=$TEST_LOADGEN_STEP)); do + TEST_LOADGEN_NUM_MESSAGES=$((TEST_LOADGEN_NUM_MESSAGES + TEST_LOADGEN_DURATION * pps)) + done + export TEST_LOADGEN_NUM_MESSAGES + # + # Starting load-generator server which will generate traffic based on env configuration + # from above. + # + printf "Starting load-generator with the following configuration:\n" + {%- for key, value in loadgen.items() %} + printf " {{ key | upper }}:%s\n" "$TEST_LOADGEN_{{ key | upper }}" + {%- endfor %} + printf " NUM_MESSAGES: %s\n" "$TEST_LOADGEN_NUM_MESSAGES" + + source /etc/freeradius/start_valgrind_profiling.sh + + detach: true + verify: + timeout: {{ test_state1_verify_timeout }} + trigger_mode: unordered diff --git a/src/tests/multi-server/tests/prof-pap-auth/5min.test.yml b/src/tests/multi-server/tests/prof-pap-auth/5min.test.yml new file mode 100644 index 00000000000..494dbd39455 --- /dev/null +++ b/src/tests/multi-server/tests/prof-pap-auth/5min.test.yml @@ -0,0 +1,17 @@ +# Topology - N/A for profiling test + +# Routing - N/A for profiling test + +# Load generator configuration +loadgen: + start_pps: 500 + max_pps: 500 + duration: 300 + step: 500 + parallel: 1 + max_backlog: 1000 + repeat: "no" + +# Test framework +test_timeout: 350 +test_verify_timeout: 340 diff --git a/src/tests/multi-server/tests/prof-pap-auth/environment.yml.j2 b/src/tests/multi-server/tests/prof-pap-auth/environment.yml.j2 new file mode 120000 index 00000000000..ced59820b6e --- /dev/null +++ b/src/tests/multi-server/tests/prof-pap-auth/environment.yml.j2 @@ -0,0 +1 @@ +../../environments/profiling-pap-auth.yml.j2 \ No newline at end of file diff --git a/src/tests/multi-server/tests/prof-pap-auth/short.ci.test.yml b/src/tests/multi-server/tests/prof-pap-auth/short.ci.test.yml new file mode 100644 index 00000000000..b009c3ed3a8 --- /dev/null +++ b/src/tests/multi-server/tests/prof-pap-auth/short.ci.test.yml @@ -0,0 +1,18 @@ +# Topology - N/A for profiling test + +# Routing - N/A for profiling test + +# Load generator configuration +loadgen: + start_pps: 100 + max_pps: 100 + duration: 60 + step: 100 + parallel: 1 + max_backlog: 1000 + repeat: "no" + +# Test framework +test_timeout: 125 +test_state1_verify_timeout: 120 +test_state2_verify_timeout: 30 diff --git a/src/tests/multi-server/tests/prof-pap-auth/template.yml.j2 b/src/tests/multi-server/tests/prof-pap-auth/template.yml.j2 new file mode 100644 index 00000000000..185bcd8b0a2 --- /dev/null +++ b/src/tests/multi-server/tests/prof-pap-auth/template.yml.j2 @@ -0,0 +1,38 @@ +timeout: {{ test_timeout }} +state_order: sequence +states: + state_1: + description: > + Baseline profiling test + host: + profiling-server: + actions: + - execute_command: + command: | + # + # proto_load configuration via environment variables + # + {%- for key, value in loadgen.items() %} + export TEST_LOADGEN_{{ key | upper }}="{{ value }}" + {%- endfor %} + TEST_LOADGEN_NUM_MESSAGES=0 + for ((pps=$TEST_LOADGEN_START_PPS; pps<=$TEST_LOADGEN_MAX_PPS; pps+=$TEST_LOADGEN_STEP)); do + TEST_LOADGEN_NUM_MESSAGES=$((TEST_LOADGEN_NUM_MESSAGES + TEST_LOADGEN_DURATION * pps)) + done + export TEST_LOADGEN_NUM_MESSAGES + # + # Starting load-generator server which will generate traffic based on env configuration + # from above. + # + printf "Starting load-generator with the following configuration:\n" + {%- for key, value in loadgen.items() %} + printf " {{ key | upper }}:%s\n" "$TEST_LOADGEN_{{ key | upper }}" + {%- endfor %} + printf " NUM_MESSAGES: %s\n" "$TEST_LOADGEN_NUM_MESSAGES" + + source /etc/freeradius/start_valgrind_profiling.sh + + detach: true + verify: + timeout: {{ test_state1_verify_timeout }} + trigger_mode: unordered