This example shows how to use the Score routing type to steer traffic towards the endpoint with
the most available capacity. Rather than relying solely on bandwidth utilization, a score can
capture any metric that matters to your environment, memory pressure, CPU load, queue depth, and
so on.
In this example each agent node runs a small script that reads the system’s free memory, converts it to a score between 1 and 100 (100 = most free memory = most preferred), and writes that score to the agent’s score file. The router then routes incoming requests to the highest-scored healthy endpoint.
See Score Routing Type for a conceptual overview and Score File for the agent configuration option.
The following script reads /proc/meminfo, calculates the ratio of available memory to total
memory, maps it to a 1–100 integer, and atomically writes the result to the score file.
#!/bin/bash
# score-memory.sh - update the agent score based on available memory.
# Schedule this with cron or a systemd timer, for example every 30 seconds.
set -euo pipefail
SCORE_FILE="/var/lib/varnish-controller/<agent-name>/score"
mem_total=$(awk '/^MemTotal:/ { print $2 }' /proc/meminfo)
mem_available=$(awk '/^MemAvailable:/ { print $2 }' /proc/meminfo)
# Score 1–100: 100 means all memory is free (most preferred).
# The result is clamped so it is never below 1.
score=$(awk "BEGIN { s = int(($mem_available / $mem_total) * 100); print (s < 1 ? 1 : s) }")
# Write atomically to avoid the router reading a partial file.
tmp=$(mktemp "$(dirname "$SCORE_FILE")/.score.XXXXXX")
echo "$score" > "$tmp"
mv "$tmp" "$SCORE_FILE"
Replace <agent-name> with the value of the agent’s -name argument (defaults to the hostname).
Install a cron job that runs the script every 30 seconds:
# /etc/cron.d/varnish-controller-score
* * * * * root /usr/local/bin/score-memory.sh
* * * * * root sleep 30 && /usr/local/bin/score-memory.sh
Or use a systemd timer for more precise control:
# /etc/systemd/system/vc-score.service
[Unit]
Description=Update Varnish Controller agent score
[Service]
Type=oneshot
ExecStart=/usr/local/bin/score-memory.sh
# /etc/systemd/system/vc-score.timer
[Unit]
Description=Run vc-score every 30 seconds
[Timer]
OnBootSec=10
OnUnitActiveSec=30s
[Install]
WantedBy=timers.target
systemctl enable --now vc-score.timer
Point the agent at the score file explicitly if it is not in the default location, or rely on the
default path <base-dir>/<agent-name>/score:
# Using the default path - no extra configuration needed.
varnish-controller-agent -name myagent
# Or override the path explicitly:
varnish-controller-agent -name myagent -score-file /var/lib/varnish-controller/myagent/score
Environment variable equivalent:
VARNISH_CONTROLLER_SCORE_FILE=/var/lib/varnish-controller/myagent/score
After the script runs, you can inspect the value that the router will see in the health check JSON:
$ cat /var/lib/varnish-controller/myagent/traffic_router_health.json | python3 -m json.tool
{
"version": 1,
"status": "healthy",
"updated_at": "2025-01-15T12:00:30Z",
"domains": null,
"mbps": 312.4,
"max_mbps": 1000.0,
"score": 74
}
Enable Score routing on an existing routing rule with vcli:
$ vcli rr update 1 --lookup-order=Score
+----+---------+------+------+-------+---------------+
| ID | Name | DNS | HTTP | Order | Debug Headers |
+----+---------+------+------+-------+---------------+
| 1 | Routing | true | true | Score | false |
+----+---------+------+------+-------+---------------+
If you want history-based stickiness as a first pass and score-based selection as a fallback, put
History first:
$ vcli rr update 1 --lookup-order=History,Score
+----+---------+------+------+---------------+---------------+
| ID | Name | DNS | HTTP | Order | Debug Headers |
+----+---------+------+------+---------------+---------------+
| 1 | Routing | true | true | History,Score | false |
+----+---------+------+------+---------------+---------------+
Enable debug headers on the routing rule to confirm which endpoint was selected and why:
$ vcli rr update 1 --debug-headers=true
Then send a request through the router:
$ curl -s -o /dev/null -v http://<router-ip>:8900 -H "Host: mysite.example.com"
< HTTP/1.1 302 Found
< X-Router-Name: router1
< X-Router-LookupTime: 8.214µs
< X-Router-ClientIP: 203.0.113.5
< X-Router-RouteType: Score
< X-Router-Endpoint: 0-agent2
< X-Router-Trace: [Score]
< Location: http://10.0.0.12:8081/mysite.example.com/
The X-Router-RouteType: Score header confirms that score-based selection was used. The request was
routed to agent2, which at that moment had the highest score (most available memory) among healthy,
non-over-utilized endpoints.